home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / a11y.js next >
Text File  |  2010-01-15  |  106KB  |  2,661 lines

  1. /* See license.txt for terms of usage */
  2. FBL.ns(function() { with (FBL) {
  3. var singleSpaceTag = DIV({'class' : 'a11y1emSize'}, "x");
  4. //************************************************************************************************
  5. //Module Management
  6. Firebug.A11yModel = extend(Firebug.Module,
  7. {
  8.     dispatchName: "a11y",
  9.  
  10.     initialize : function()
  11.     {
  12.         this.handleTabBarFocus = bind(this.handleTabBarFocus, this);
  13.         this.handleTabBarBlur = bind(this.handleTabBarBlur, this);
  14.         this.handlePanelBarKeyPress = bind(this.handlePanelBarKeyPress, this);
  15.         this.onNavigablePanelKeyPress = bind(this.onNavigablePanelKeyPress, this);
  16.         this.onConsoleMouseDown = bind(this.onConsoleMouseDown, this);
  17.         this.onLayoutKeyPress = bind(this.onLayoutKeyPress, this);
  18.         this.onCSSKeyPress = bind(this.onCSSKeyPress, this);
  19.         this.onCSSMouseDown = bind(this.onCSSMouseDown, this);
  20.         this.onHTMLKeyPress = bind(this.onHTMLKeyPress, this);
  21.         this.onHTMLFocus = bind(this.onHTMLFocus, this);
  22.         this.onHTMLBlur = bind(this.onHTMLBlur, this);
  23.         this.onPanelFocus = bind(this.onPanelFocus, this);
  24.         this.onLayoutFocus = bind(this.onLayoutFocus, this);
  25.         this.onLayoutBlur = bind(this.onLayoutBlur, this);
  26.         this.onScriptContextMenu = bind(this.onScriptContextMenu, this);
  27.         this.onCSSPanelContextMenu = bind(this.onCSSPanelContextMenu, this);
  28.         this.onScriptKeyPress = bind(this.onScriptKeyPress, this);
  29.         this.onScriptKeyUp = bind(this.onScriptKeyUp, this);
  30.         this.onScriptMouseUp = bind(this.onScriptMouseUp, this);
  31.         this.onNetKeyPress = bind(this.onNetKeyPress, this);
  32.         this.onNetMouseDown = bind(this.onNetMouseDown, this);
  33.         this.onNetFocus = bind(this.onNetFocus, this);
  34.         this.onNetBlur = bind(this.onNetBlur, this);
  35.         Firebug.chrome.window.a11yEnabled = false; // mark ourselves disabled so we don't performDisable() if we are not enabled.
  36.         Firebug.Debugger.addListener(this);
  37.     },
  38.  
  39.     initializeUI : function()
  40.     {
  41.         //Initialize according to the current pref value.
  42.         this.updateOption("a11y.enable", this.isEnabled());
  43.     },
  44.  
  45.     isEnabled : function()
  46.     {
  47.         return Firebug.getPref("extensions.firebug", "a11y.enable");
  48.     },
  49.  
  50.     updateOption: function(name, value)
  51.     {
  52.         if (name == "a11y.enable")
  53.         {
  54.             // Update for current chrome
  55.             this.set(value, Firebug.chrome);
  56.             // If the current chrome is external window, update also original chrome.
  57.             if (Firebug.chrome != Firebug.originalChrome)
  58.             {
  59.                 this.set(value, Firebug.originalChrome);
  60.             }
  61.         }
  62.     },
  63.  
  64.     reattachContext : function(browser, context)
  65.     {
  66.         if (this.isEnabled())
  67.             this.set(true, Firebug.chrome);
  68.     },
  69.  
  70.     set : function(enable, chrome)
  71.     {
  72.         if (chrome.window.a11yEnabled == enable)
  73.             return;
  74.         if (enable)
  75.             this.performEnable(chrome);
  76.         else
  77.             this.performDisable(chrome);
  78.         chrome.window.a11yEnabled = enable;
  79.     },
  80.  
  81.     performEnable : function(chrome)
  82.     {
  83.         //add class used by all a11y related css styles (e.g. :focus and -moz-user-focus styles)
  84.         setClass(chrome.$('fbContentBox'), 'useA11y');
  85.         setClass(chrome.$('fbStatusBar'), 'useA11y');
  86.         //manage all key events in toolbox (including tablists)
  87.         chrome.$('fbStatusPrefix').setAttribute('value', $STR("a11y.labels.firebug status"));
  88.         chrome.$("fbContentBox").addEventListener("keypress", this.handlePanelBarKeyPress , true);
  89.         //make focus stick to inspect button when clicked
  90.         chrome.$("fbInspectButton").addEventListener("mousedown", this.focusTarget, true);
  91.         chrome.$('fbPanelBar1-panelTabs').addEventListener('focus', this.handleTabBarFocus, true);
  92.         chrome.$('fbPanelBar1-panelTabs').addEventListener('blur', this.handleTabBarBlur, true);
  93.         chrome.$('fbPanelBar2-panelTabs').addEventListener('focus', this.handleTabBarFocus, true);
  94.         chrome.$('fbPanelBar2-panelTabs').addEventListener('blur', this.handleTabBarBlur, true);
  95.         setClass(chrome.$("fbPanelBar1").browser.contentDocument.body, 'useA11y');
  96.         setClass(chrome.$("fbPanelBar2").browser.contentDocument.body, 'useA11y');
  97.         Firebug.Editor.addListener(this);
  98.     },
  99.  
  100.     performDisable : function(chrome)
  101.     {
  102.         //undo everything we did in performEnable
  103.         removeClass(chrome.$('fbContentBox'), 'useA11y');
  104.         removeClass(chrome.$('fbStatusBar'), 'useA11y');
  105.         chrome.$("fbPanelBar1").removeEventListener("keypress", this.handlePanelBarKeyPress , true);
  106.         chrome.$("fbInspectButton").removeEventListener("mousedown", this.focusTarget, true);
  107.         chrome.$('fbPanelBar1-panelTabs').removeEventListener('focus', this.handleTabBarFocus, true);
  108.         chrome.$('fbPanelBar1-panelTabs').removeEventListener('blur', this.handleTabBarBlur, true);
  109.         chrome.$('fbPanelBar2-panelTabs').removeEventListener('focus', this.handleTabBarFocus, true);
  110.         chrome.$('fbPanelBar2-panelTabs').removeEventListener('blur', this.handleTabBarBlur, true);
  111.         removeClass(chrome.$("fbPanelBar1").browser.contentDocument.body, 'useA11y');
  112.         removeClass(chrome.$("fbPanelBar2").browser.contentDocument.body, 'useA11y');
  113.         Firebug.Editor.removeListener(this);
  114.         chrome.$("fbPanelBar1").browser.setAttribute('showcaret', false);
  115.     },
  116.  
  117.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  118.     // Context & Panel Management
  119.  
  120.     onInitializeNode : function(panel, actAsPanel)
  121.     {
  122.         var panelA11y = this.getPanelA11y(panel, true);
  123.         if (!panelA11y)
  124.             return;
  125.         panelA11y.tabStop = null;
  126.         panelA11y.manageFocus = false;
  127.         panelA11y.lastIsDefault = false;
  128.         actAsPanel = actAsPanel ? actAsPanel : panel.name;
  129.         //panel.panelNode.ownerDocument.addEventListener("focus", this.reportFocus, true);
  130.         this.makeFocusable(panel.panelNode, false);
  131.         switch (actAsPanel)
  132.         {
  133.             case 'console':
  134.                 panelA11y.manageFocus = true;
  135.                 if (panel.name == "console")
  136.                 {
  137.                     panel.panelNode.setAttribute('aria-label', $STR('a11y.labels.log rows'));
  138.                     panelA11y.lastIsDefault = true;
  139.                     panel.panelNode.setAttribute('role', 'list');
  140.                 }
  141.                 else if (panel.name == "callstack")
  142.                 {
  143.                     panel.panelNode.setAttribute('role', 'list');
  144.                     panel.panelNode.setAttribute('aria-label', $STR('a11y.labels.call stack'));
  145.                 }
  146.                 else
  147.                     panel.panelNode.setAttribute('role', 'presentation');
  148.                 //panel.panelNode.setAttribute('aria-live', 'polite');
  149.                 panel.panelNode.addEventListener("keypress", this.onNavigablePanelKeyPress, false);
  150.                 panel.panelNode.addEventListener("focus", this.onPanelFocus, true);
  151.                 panel.panelNode.addEventListener("mousedown", this.onConsoleMouseDown, false);
  152.                 if (panel.name == "breakpoints")
  153.                     panel.panelNode.style.overflowX = "hidden";
  154.                 break;
  155.             case 'html':
  156.                 panel.panelNode.setAttribute('role', 'tree');
  157.                 panel.panelNode.setAttribute('aria-label', $STR('a11y.labels.document structue'));
  158.                 panel.panelNode.addEventListener("keypress", this.onHTMLKeyPress, false);
  159.                 panel.panelNode.addEventListener("focus", this.onHTMLFocus, true);
  160.                 panel.panelNode.addEventListener("blur", this.onHTMLBlur, true);
  161.                 break;
  162.             case 'css':
  163.                 panelA11y.manageFocus = true;
  164.                 panel.panelNode.addEventListener("keypress", this.onCSSKeyPress, false);
  165.                 panel.panelNode.addEventListener("mousedown", this.onCSSMouseDown, false);
  166.                 panel.panelNode.addEventListener("focus", this.onPanelFocus, true);
  167.                 panel.panelNode.addEventListener('contextmenu', this.onCSSPanelContextMenu, false)
  168.                 this.insertHiddenText(panel, panel.panelNode, $STR('a11y.labels.overridden'), false, "CSSOverriddenDescription");
  169.                 panel.panelNode.setAttribute('role', panel.name == "stylesheet" ? 'list' : "presentation");
  170.                 break;
  171.             case 'layout':
  172.                 panelA11y.manageFocus = true;
  173.                 panel.panelNode.addEventListener("keypress", this.onLayoutKeyPress, false);
  174.                 panel.panelNode.addEventListener("focus", this.onLayoutFocus, true);
  175.                 panel.panelNode.addEventListener("blur", this.onLayoutBlur, true);
  176.                 break;
  177.             case 'script':
  178.                 panel.panelNode.addEventListener('contextmenu', this.onScriptContextMenu, true);
  179.                 panel.panelNode.addEventListener('keypress', this.onScriptKeyPress, true);
  180.                 panel.panelNode.addEventListener('keyup', this.onScriptKeyUp, true);
  181.                 panel.panelNode.addEventListener('mouseup', this.onScriptMouseUp, true);
  182.                 panelA11y.oneEmElem = this.addSingleSpaceElem(panel.panelNode);
  183.                 break;
  184.             case 'net':
  185.                 panelA11y.manageFocus = true;
  186.                 panel.panelNode.addEventListener("keypress", this.onNavigablePanelKeyPress, false);
  187.                 panel.panelNode.addEventListener("focus", this.onPanelFocus, true);
  188.                 panel.panelNode.addEventListener("focus", this.onNetFocus, true);
  189.                 panel.panelNode.addEventListener("blur", this.onNetBlur, true);
  190.                 panel.panelNode.addEventListener("mousedown", this.onNetMouseDown, false);
  191.                 break;
  192.         }
  193.     },
  194.  
  195.     onDestroyNode : function(panel, actAsPanel)
  196.     {
  197.         var panelA11y = this.getPanelA11y(panel);
  198.         if (!panelA11y)
  199.             return;
  200.         panelA11y = null;
  201.         actAsPanel = actAsPanel ? actAsPanel : panel.name;
  202.         //remove all event handlers we added in onInitializeNode
  203.         switch (actAsPanel)
  204.         {
  205.             case 'console':
  206.                 panel.panelNode.removeEventListener("keypress", this.onNavigablePanelKeyPress, false);
  207.                 panel.panelNode.removeEventListener("focus", this.onPanelFocus, true);
  208.                 panel.panelNode.removeEventListener("mousedown", this.onConsoleMouseDown, false);
  209.                 break;
  210.             case 'html':
  211.                 panel.panelNode.removeEventListener("keypress", this.onHTMLKeyPress, false);
  212.                 panel.panelNode.removeEventListener("focus", this.onHTMLFocus, true);
  213.                 panel.panelNode.removeEventListener("blur", this.onHTMLBlur, true);
  214.                 break;
  215.             case 'css':
  216.                 panel.panelNode.removeEventListener("keypress", this.onCSSKeyPress, false);
  217.                 panel.panelNode.removeEventListener("mousedown", this.onCSSMouseDown, false);
  218.                 panel.panelNode.removeEventListener("focus", this.onPanelFocus, true);
  219.                 panel.panelNode.removeEventListener("blur", this.onPanelBlur, true);
  220.                 panel.panelNode.removeEventListener('contextmenu', this.onCSSPanelContextMenu, false)
  221.                 break;
  222.             case 'layout':
  223.                 panel.panelNode.removeEventListener("keypress", this.onLayoutKeyPress, false);
  224.                 panel.panelNode.removeEventListener("focus", this.onLayoutFocus, true);
  225.                 panel.panelNode.removeEventListener("blur", this.onLayoutBlur, true);
  226.                 break;
  227.             case 'script':
  228.                 panel.panelNode.removeEventListener('contextmenu', this.onScriptContextMenu, true);
  229.                 panel.panelNode.removeEventListener('keypress', this.onScriptKeyPress, true);
  230.                 panel.panelNode.removeEventListener('keyup', this.onScriptKeyUp, true);
  231.                 panel.panelNode.removeEventListener('mouseup', this.onScriptMouseUp, true)
  232.                 break;
  233.             case 'net':
  234.                 panel.panelNode.removeEventListener("keypress", this.onNavigablePanelKeyPress, false);
  235.                 panel.panelNode.removeEventListener("focus", this.onPanelFocus, true);
  236.                 panel.panelNode.removeEventListener("focus", this.onNetFocus, true);
  237.                 panel.panelNode.removeEventListener("blur", this.onNetBlur, true);
  238.                 panel.panelNode.removeEventListener("mousedown", this.onNetMouseDown, false);
  239.                 break;
  240.         }
  241.     },
  242.  
  243.     showPanel : function(browser, panel)
  244.     {
  245.         var panelA11y = this.getPanelA11y(panel);
  246.         if (!panelA11y)
  247.             return;
  248.         var title = panel.name;
  249.         var panelType = Firebug.getPanelType(panel.name);
  250.         if (panelType)
  251.             title = Firebug.getPanelTitle(panelType);
  252.         Firebug.chrome.$('fbToolbar').setAttribute('aria-label', title + " " + $STR("a11y.labels.panel tools"))
  253.         var panelBrowser = Firebug.chrome.getPanelBrowser(panel);
  254.         panelBrowser.setAttribute('showcaret', (panel.name == "script"));
  255.         panelBrowser.contentDocument.body.setAttribute('aria-label', $STRF("a11y.labels.title panel", [title]));
  256.     },
  257.  
  258.     showSidePanel : function(browser, sidePanel)
  259.     {
  260.         var panelA11y = this.getPanelA11y(sidePanel);
  261.         if (!panelA11y)
  262.             return;
  263.         var panelBrowser = Firebug.chrome.getPanelBrowser(sidePanel);
  264.         var panelType = Firebug.getPanelType(sidePanel.name);
  265.         if (panelType)
  266.             title = Firebug.getPanelTitle(panelType);
  267.             panelBrowser.contentDocument.body.setAttribute('aria-label', $STRF("a11y.labels.title side panel", [title]));
  268.     },
  269.  
  270.     addLiveElem : function(panel, role, politeness)
  271.     {
  272.         var panelA11y = this.getPanelA11y(panel);
  273.         if (!panelA11y)
  274.             return;
  275.         if (panelA11y.liveElem && isElement(panelA11y.liveElem))
  276.             return;
  277.         var attrName = attrValue = "";
  278.         if (role)
  279.         {
  280.             attrName = 'role';
  281.             attrValue = role;
  282.         }
  283.         else
  284.         {
  285.             attrName = "aria-live";
  286.             attrValue = politeness ? politeness : 'polite';
  287.         }
  288.         var elem = panel.document.createElement('div');
  289.         elem.setAttribute(attrName, attrValue);
  290.         elem.className = "offScreen";
  291.         panel.document.body.appendChild(elem);
  292.         panelA11y.liveElem = elem;
  293.         return elem;
  294.     },
  295.  
  296.     updateLiveElem: function(panel, msg, useAlert)
  297.     {
  298.         var panelA11y = this.getPanelA11y(panel);
  299.         if (!panelA11y)
  300.             return;
  301.         var elem = panelA11y.liveElem;
  302.         if (!elem)
  303.             elem = this.addLiveElem(panel);
  304.         elem.textContent = msg;
  305.         if (useAlert)
  306.             elem.setAttribute('role', 'alert');
  307.     },
  308.  
  309.     addSingleSpaceElem : function(parent)
  310.     {
  311.         return singleSpaceTag.append({}, parent, this);
  312.     },
  313.  
  314.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  315.     // Toolbars & Tablists
  316.  
  317.     focusTarget : function(event)
  318.     {
  319.         this.focus(event.target);
  320.     },
  321.  
  322.     handlePanelBarKeyPress : function (event)
  323.     {
  324.         var target = event.originalTarget;
  325.         var isTab = target.nodeName.toLowerCase() == "paneltab";
  326.         var isButton = target.nodeName.search(/(xul:)?toolbarbutton/) != -1;
  327.         var isDropDownMenu = isButton && (target.getAttribute('type') == "menu" || target.id == "fbLocationList") ;
  328.         var siblingTab, forward, toolbar, buttons;
  329.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  330.         if (keyCode == KeyEvent.DOM_VK_TAB)
  331.             this.ensurePanelTabStops(); //TODO: need a better solution to prevent loss of panel tabstop
  332.         if (isTab || isButton )
  333.         {
  334.             switch (keyCode)
  335.             {
  336.                 case KeyEvent.DOM_VK_LEFT:
  337.                 case KeyEvent.DOM_VK_RIGHT:
  338.                     forward = event.keyCode == KeyEvent.DOM_VK_RIGHT;
  339.                     if (isTab)
  340.                     {
  341.                         //will only work as long as long as siblings only consist of paneltab elements
  342.                         siblingTab = target[forward ? 'nextSibling' : 'previousSibling'];
  343.                         if (!siblingTab)
  344.                             siblingTab = target.parentNode[forward ? 'firstChild' : 'lastChild'];
  345.                         if (siblingTab)
  346.                         {
  347.                             var panelBar = getAncestorByClass(target, 'panelBar')
  348.                             setTimeout(bindFixed(function()
  349.                             {
  350.                                 panelBar.selectTab(siblingTab);
  351.                                 this.focus(siblingTab);
  352.                             }, this));
  353.                         }
  354.                    }
  355.                    else if (isButton)
  356.                    {
  357.                        if (target.id=="fbFirebugMenu" && !forward)
  358.                        {
  359.                             cancelEvent(event);
  360.                             return;
  361.                        }
  362.                        toolbar = getAncestorByClass(target, 'innerToolbar');
  363.                        if (toolbar)
  364.                        {
  365.                            var doc = target.ownerDocument;
  366.                            //temporarily make all buttons in the toolbar part of the tab order,
  367.                            //to allow smooth, native focus advancement
  368.                            setClass(toolbar, 'hasTabOrder');
  369.                            doc.commandDispatcher[forward ? 'advanceFocus' : 'rewindFocus']();
  370.                            //Very ugly hack, but it works well. This prevents focus to 'spill out' of a
  371.                            //toolbar when using the left and right arrow keys
  372.                            if (!isAncestor(doc.commandDispatcher.focusedElement, toolbar))
  373.                            {
  374.                                //we moved focus to somewhere out of the toolbar: not good. Move it back to where it was.
  375.                                doc.commandDispatcher[!forward ? 'advanceFocus' : 'rewindFocus']();
  376.                            }
  377.                            //remove the buttons from the tab order again, so that it will remain uncluttered
  378.                            removeClass(toolbar, 'hasTabOrder');
  379.                        }
  380.                         cancelEvent(event);
  381.                         return;
  382.                    }
  383.                 break;
  384.                 case KeyEvent.DOM_VK_RETURN:
  385.                 case KeyEvent.DOM_VK_SPACE:
  386.                 case KeyEvent.DOM_VK_UP:
  387.                 case KeyEvent.DOM_VK_DOWN:
  388.                     if (isTab && target.tabMenu)
  389.                         target.tabMenu.popup.showPopup(target.tabMenu, -1, -1, "popup", "bottomleft", "topleft");
  390.                     else if (isButton)
  391.                     {
  392.                         if (isDropDownMenu)
  393.                         {
  394.                             if (target.id == "fbLocationList")
  395.                                 target.showPopup();
  396.                             else
  397.                                 target.open = true;
  398.                         }
  399.                         cancelEvent(event);
  400.                         return false;
  401.                     }
  402.                 break;
  403.                 case KeyEvent.DOM_VK_F4:
  404.                     if (isTab && target.tabMenu)
  405.                         target.tabMenu.popup.showPopup(target.tabMenu, -1, -1, "popup", "bottomleft", "topleft");
  406.                 break;
  407.             }
  408.         }
  409.     },
  410.  
  411.     handleTabBarFocus: function(event)
  412.     {
  413.         this.tabFocused = true;
  414.     },
  415.  
  416.     handleTabBarBlur: function(event)
  417.     {
  418.         this.tabFocused = false;
  419.     },
  420.  
  421.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  422.     // Panel Focus & Tab Order Management
  423.  
  424.     getPanelTabStop : function(panel)
  425.     {
  426.         var panelA11y = this.getPanelA11y(panel);
  427.         if (panelA11y)
  428.             return panelA11y.tabStop;
  429.         return null;
  430.     },
  431.  
  432.     ensurePanelTabStops: function()
  433.     {
  434.         if (!FirebugContext || !FirebugContext.chrome)
  435.             return;
  436.         var panel = Firebug.chrome.getSelectedPanel();
  437.         var sidePanel = Firebug.chrome.getSelectedSidePanel();
  438.         this.ensurePanelTabStop(panel);
  439.         if (sidePanel)
  440.             this.ensurePanelTabStop(sidePanel);
  441.     },
  442.  
  443.     ensurePanelTabStop: function(panel)
  444.     {
  445.         var panelA11y = this.getPanelA11y(panel);
  446.         if (!panelA11y)
  447.             return;
  448.         if (panelA11y.manageFocus)
  449.         {
  450.             var tabStop = this.getPanelTabStop(panel);
  451.             if (!tabStop || !this.isVisibleByStyle(tabStop) || !isVisible(tabStop))
  452.             {
  453.                 this.tabStop = null;
  454.                 this.findPanelTabStop(panel, 'focusRow', panelA11y.lastIsDefault);
  455.             }
  456.             else if (tabStop.getAttribute('tabindex') !== "0")
  457.                 tabStop.setAttribute('tabindex', "0");
  458.         if (tabStop)
  459.             this.checkModifiedState(panel, tabStop, true);
  460.         }
  461.     },
  462.  
  463.     checkModifiedState : function(panel, elem, makeTab)
  464.     {
  465.         var panelA11y = this.getPanelA11y(panel);
  466.         if (!panelA11y || !elem)
  467.             return;
  468.         switch (panel.name)
  469.         {
  470.             case  'console' :
  471.                 if (hasClass(elem, 'focusRow'))
  472.                     this.modifyPanelRow(panel, elem, makeTab);
  473.                 break;
  474.         }
  475.     },
  476.  
  477.     setPanelTabStop : function (panel, elem)
  478.     {
  479.         var panelA11y = this.getPanelA11y(panel);
  480.         if (!panelA11y)
  481.             return;
  482.         var tabStop = this.getPanelTabStop(panel)
  483.         if (tabStop)
  484.             this.makeFocusable(tabStop, false);
  485.         panelA11y.tabStop = elem;
  486.         if (elem)
  487.         {
  488.             panelA11y.reFocusId = null;
  489.             this.makeFocusable(elem, true);
  490.         }
  491.     },
  492.  
  493.     findPanelTabStop : function(panel, className, last)
  494.     {
  495.         var candidates = panel.panelNode.getElementsByClassName(className);
  496.         candidates= Array.filter(candidates, function(e,i,a){return this.isVisibleByStyle(e) && isVisible(e);}, this);
  497.         if (candidates.length > 0)
  498.         {
  499.             var chosenRow = candidates[last ? candidates.length -1 : 0];
  500.             this.modifyPanelRow(panel, chosenRow, true)
  501.             this.setPanelTabStop(panel, chosenRow);
  502.         }
  503.         else
  504.             this.setPanelTabStop(panel, null);
  505.     },
  506.  
  507.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  508.     // Console Panel
  509.  
  510.     onLogRowCreated : function(panel, row)
  511.     {
  512.         var panelA11y = this.getPanelA11y(panel);
  513.         if (!panelA11y)
  514.             return;
  515.         if (hasClass(row, 'logRow-dir'))
  516.         {
  517.             row.setAttribute('role', 'listitem');
  518.             setClass(row, 'outerFocusRow');
  519.             var memberRows = row.getElementsByClassName('memberRow');
  520.             if (memberRows.length > 0)
  521.                 this.onMemberRowsAdded(panel, memberRows);
  522.         }
  523.         else if (hasClass(row, 'logRow-group') || hasClass(row, 'logRow-profile'))
  524.         {
  525.             row.setAttribute('role', 'presentation');
  526.             var focusRow = row.getElementsByClassName('logGroupLabel').item(0);
  527.             if (focusRow)
  528.             {
  529.                 this.setPanelTabStop(panel, focusRow);
  530.                 focusRow.setAttribute('aria-expanded', hasClass(row, 'opened') + "");
  531.                 if (!hasClass(row, 'logRow-profile'))
  532.                     this.insertHiddenText(panel, focusRow, 'group label: ');
  533.             }
  534.         }
  535.         else if (hasClass(row, 'logRow-errorMessage') || hasClass(row, 'logRow-warningMessage'))
  536.         {
  537.             setClass(row, 'outerFocusRow');
  538.             row.setAttribute('role', 'presentation');
  539.             var focusRow = row.getElementsByClassName('errorTitle').item(0);
  540.             if (focusRow)
  541.             {
  542.                 this.setPanelTabStop(panel, focusRow);
  543.                 focusRow.setAttribute('aria-expanded', hasClass(focusRow.parentNode, 'opened') + "");
  544.             }
  545.         }
  546.         else if (hasClass(row, 'logRow-stackTrace'))
  547.         {
  548.             setClass(row, 'outerFocusRow');
  549.             row.setAttribute('role', 'listitem');
  550.             var stackFrames = row.getElementsByClassName('focusRow');
  551.             Array.forEach(stackFrames, function(e,i,a){
  552.                 e.setAttribute('role', 'listitem');
  553.                 if ((panelA11y.lastIsDefault && i === stackFrames.length - 1) || (!panelA11y.lastIsDefault && i === 0))
  554.                     this.setPanelTabStop(panel, e);
  555.                 else
  556.                     this.makeFocusable(e, false);
  557.                 }, this);
  558.         }
  559.         else if (hasClass(row, 'logRow-spy'))
  560.         {
  561.             var focusRow = getChildByClass(row, 'spyHeadTable');
  562.             if (focusRow)
  563.                 this.makeFocusable(focusRow, true);
  564.         }
  565.         else
  566.         {
  567.             row.setAttribute('role', 'listitem');
  568.             setClass(row, 'focusRow');
  569.             setClass(row, 'outerFocusRow');
  570.             if (isVisible(row))
  571.                 this.setPanelTabStop(panel, row);
  572.         }
  573.     },
  574.  
  575.     modifyLogRow :function(panel, row, inTabOrder)
  576.     {
  577.         this.makeFocusable(row, inTabOrder);
  578.         var logRowType = this.getLogRowType(row);
  579.         if (logRowType)
  580.             this.insertHiddenText(panel, row, logRowType + ": ");
  581.         var arrayNode = getChildByClass(row, 'objectBox-array');
  582.         if (arrayNode)
  583.         {
  584.             arrayNode.setAttribute('role', 'group');
  585.             this.insertHiddenText(panel, row, "array" + ": ");
  586.         }
  587.         var focusObjects = this.getFocusObjects(row );
  588.         Array.forEach(focusObjects, function(e,i,a){
  589.             this.makeFocusable(e);
  590.             var prepend = "";
  591.             var append = " (" + this.getObjectType(e) + ") ";
  592.             if (e.textContent != "")
  593.                 e.setAttribute('aria-label', prepend + e.textContent + append);
  594.             if (arrayNode)
  595.                 e.setAttribute('role', 'listitem');
  596.             }, this);
  597.     },
  598.  
  599.     onNavigablePanelKeyPress : function(event)
  600.     {
  601.         var target = event.target;
  602.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  603.         if (!this.isTabWorthy(target) && !this.isFocusNoTabObject(target))
  604.             return;
  605.         else if (event.shiftKey || event.altKey)
  606.             return;
  607.         else if ([13, 32, 33, 34, 35, 36, 37, 38, 39, 40, 46].indexOf(keyCode) == -1)
  608.             return;//not interested in any other keys, than arrows, pg, home/end, del space & enter
  609.         var panel = Firebug.getElementPanel(target)
  610.         var panelA11y = this.getPanelA11y(panel);
  611.         if (!panelA11y)
  612.             return;
  613.         var newTarget = target
  614.         if (!this.isOuterFocusRow(target))
  615.         {
  616.             if (event.ctrlKey)
  617.             {
  618.                 newTarget = this.getAncestorRow(target);
  619.                 if (newTarget)
  620.                     newTarget = [33, 38].indexOf(keyCode) == -1 ? this.getLastFocusChild(newTarget) : this.getFirstFocusChild(newTarget)
  621.             }
  622.             else if (!this.isDirCell(target) || hasClass(target, 'netInfoTab') || hasClass(target, 'netCol'))
  623.                 newTarget = this.getAncestorRow(target, true);
  624.             if (!newTarget)
  625.                 newTarget = target;
  626.         }
  627.         switch (keyCode)
  628.         {
  629.             case 38://up
  630.             case 40://down
  631.             if (!this.isFocusNoTabObject(target))
  632.             {
  633.                 this.focusSiblingRow(panel, newTarget, keyCode == 38);
  634.                 cancelEvent(event);
  635.             }
  636.                 break;
  637.             case 37://left
  638.             case 39://right
  639.             var goLeft = keyCode == 37;
  640.             if (this.isDirCell(target))
  641.             {
  642.                 var row = getAncestorByClass(target, 'memberRow');
  643.                 var toggleElem = getChildByClass(row.cells[1], "memberLabel")
  644.                 if (!goLeft && hasClass(row, 'hasChildren'))
  645.                 {
  646.                     if (hasClass(row, 'opened'))
  647.                         this.focusSiblingRow(panel, target , false);
  648.                     else if (toggleElem)
  649.                     {
  650.                         if (hasClass(row, 'hasChildren'))
  651.                             target.setAttribute('aria-expanded', 'true');
  652.                         this.dispatchMouseEvent(toggleElem, 'click');
  653.                     }
  654.                 }
  655.                 else if (goLeft)
  656.                 {
  657.                     var level = parseInt(row.getAttribute("level"));
  658.                     if (hasClass(row, 'opened'))
  659.                     {
  660.                         if (hasClass(row, 'hasChildren'))
  661.                             target.setAttribute('aria-expanded', 'false');
  662.                         this.dispatchMouseEvent(toggleElem, 'click');
  663.                     }
  664.                     else if (level > 0)
  665.                     {
  666.                         var targetLevel = (level - 1) + "";
  667.                         var newRows = Array.filter(row.parentNode.rows, function(e,i,a){
  668.                             return e.rowIndex < row.rowIndex && e.getAttribute('level') == targetLevel;
  669.                             }, this);
  670.                         if (newRows.length)
  671.                             this.focus(newRows[newRows.length -1].cells[2].firstChild);
  672.                     }
  673.                 }
  674.                 cancelEvent(event);
  675.             }
  676.             else if (this.isOuterFocusRow(target, true))
  677.             {
  678.                 if (target.hasAttribute('aria-expanded'))
  679.                 {
  680.                     if (target.getAttribute('role') == 'row' || hasClass(target, 'spyHeadTable'))
  681.                     {
  682.                         if (goLeft && target.getAttribute('aria-expanded') == "true")
  683.                         {
  684.                             var toggleElem = hasClass(target, 'spyHeadTable') ? target.getElementsByClassName('spyTitleCol').item(0) : target;
  685.                             if (toggleElem)
  686.                                 this.dispatchMouseEvent(toggleElem, 'click');
  687.                         }
  688.                     }
  689.                     else if (target.getAttribute('aria-expanded') == (goLeft ? "true" : "false"))
  690.                         this.dispatchMouseEvent(target, hasClass(target, 'logGroupLabel') ? 'mousedown' : 'click');
  691.                 }
  692.                 if (goLeft)
  693.                 {
  694.                     //check if we're in an expanded section
  695.                     var inExpanded = false, groupClass, groupLabelClass, group, groupLabel;
  696.                     if (hasClass(target, 'objectBox-stackFrame'))
  697.                     {
  698.                         inExpanded = true;
  699.                         groupClass = "errorTrace";
  700.                         groupLabelClass = "errorTitle";
  701.                     }
  702.                     else if (getAncestorByClass(target, 'logGroupBody'))
  703.                     {
  704.                         inExpanded = true;
  705.                         groupClass = "logGroupBody";
  706.                         groupLabelClass = "logGroupLabel";
  707.                     }
  708.                     if (inExpanded)
  709.                     {
  710.                         group = getAncestorByClass(target, groupClass);
  711.                         if (group)
  712.                         {
  713.                             groupLabel = this.getPreviousByClass(target, groupLabelClass, false, panel.panelNode);
  714.                             if (groupLabel)
  715.                             {
  716.                                 this.modifyPanelRow(panel, groupLabel);
  717.                                 this.focus(groupLabel);
  718.                             }
  719.                         }
  720.                     }
  721.                 }
  722.                 else if (!goLeft)
  723.                 {
  724.  
  725.                     var focusItems = this.getFocusObjects(target);
  726.                     if (focusItems.length > 0)
  727.                         this.focus(event.ctrlKey ? focusItems[focusItems.length -1] : focusItems[0]);
  728.                 }
  729.             }
  730.             else if (this.isFocusObject(target))
  731.             {
  732.                 var parentRow = this.getAncestorRow(target, true);
  733.                 var focusObjects = this.getFocusObjects(parentRow);
  734.                 if (!event.ctrlKey)
  735.                 {
  736.                     var focusIndex = Array.indexOf(focusObjects, target);
  737.                     var newIndex = goLeft ? --focusIndex : ++focusIndex;
  738.                     if (goLeft && newIndex < 0)
  739.                         this.focus( parentRow);
  740.                     else
  741.                         this.focus(focusObjects[newIndex]);
  742.                 }
  743.                 else
  744.                     this.focus(goLeft ? parentRow : focusObjects[focusObjects.length -1]);
  745.                 cancelEvent(event);
  746.             }
  747.             break;
  748.         case 35://end
  749.         case 36://home
  750.             this.focusEdgeRow(panel, newTarget, keyCode == 36);
  751.             cancelEvent(event);
  752.             break;
  753.         case 33://pgup
  754.         case 34://pgdn
  755.             this.focusPageSiblingRow(panel, newTarget, keyCode == 33);
  756.             cancelEvent(event);
  757.             break;
  758.         case 13://enter
  759.             if (this.isFocusObject(target))
  760.             {
  761.                 this.dispatchMouseEvent(target, 'click');
  762.             }
  763.             else if(hasClass(target, 'watchEditBox'))
  764.             {
  765.                 this.dispatchMouseEvent(target, 'mousedown');
  766.                 cancelEvent(event);
  767.             }
  768.             else if (hasClass(target, 'breakpointRow'))
  769.             {
  770.                 var sourceLink = target.getElementsByClassName("objectLink-sourceLink").item(0);
  771.                 if (sourceLink)
  772.                     this.dispatchMouseEvent(sourceLink, 'click');
  773.             }
  774.             else if (target.hasAttribute('aria-expanded') && (target.getAttribute('role') == 'row' || target.getAttribute('role') == 'listitem'))
  775.             {
  776.                 var toggleElem = hasClass(target, 'spyHeadTable') ? target.getElementsByClassName('spyTitleCol').item(0) : target;
  777.                 if (toggleElem)
  778.                     this.dispatchMouseEvent(toggleElem, 'click');
  779.             }
  780.             break;
  781.         case 32://space
  782.         if (this.isFocusObject(target) && target.hasAttribute('role', 'checkbox'))
  783.         {
  784.             this.dispatchMouseEvent(target, 'click');
  785.             var objectBox = getAncestorByClass(target, 'hasBreakSwitch');
  786.             if (objectBox)
  787.                 target.setAttribute('aria-checked', hasClass(objectBox, 'breakForError') + "");
  788.         }
  789.         else if (hasClass(target, 'breakpointRow'))
  790.         {
  791.             var checkbox = target.getElementsByClassName('breakpointCheckbox').item(0);
  792.             if (checkbox)
  793.             {
  794.                 target.setAttribute('aria-checked', checkbox.checked ? "false" : "true");
  795.                 this.dispatchMouseEvent(checkbox, 'click');
  796.             }
  797.         }
  798.         break;
  799.         case 46://del
  800.             if (hasClass(target, 'breakpointRow'))
  801.             {
  802.                 var closeBtn = target.getElementsByClassName('closeButton').item(0);
  803.                 if (closeBtn)
  804.                 {
  805.                     var prevBreakpoint = getPreviousByClass(target, 'breakpointRow');
  806.                     if (prevBreakpoint)
  807.                         this.makeFocusable(prevBreakpoint, true);
  808.                     Firebug.chrome.window.document.commandDispatcher.rewindFocus();
  809.                     this.dispatchMouseEvent(closeBtn, 'click');
  810.                 }
  811.             }
  812.             break;
  813.         }
  814.     },
  815.  
  816.     focusPanelRow : function(panel, row)
  817.     {
  818.         var panelA11y = this.getPanelA11y(panel);
  819.         if (!panelA11y || !row)
  820.             return;
  821.         this.modifyPanelRow(panel, row, false);
  822.         if (panelA11y.cellIndex !== undefined && row.cells && row.cells[panelA11y.cellIndex]) //allows up / down navigation in columns, if columns are used in this panel
  823.         {
  824.             var cell = row.cells[panelA11y.cellIndex];
  825.             if (!hasClass(cell, "a11yFocus"))
  826.                 cell = getChildByClass(cell, 'a11yFocus');
  827.             this.focus(cell);
  828.         }
  829.         else if (hasClass(row, 'netInfoTabs')) // for Net Panel. Focus selected tab rather than the tablist
  830.         {
  831.             var tabs = row.getElementsByClassName('netInfoTab');
  832.             tabs = Array.filter(tabs, function(e,i,a){return e.hasAttribute('selected');})
  833.             this.focus(tabs.length > 0 ? tabs[0] : row);
  834.         }
  835.         else
  836.             this.focus(row);
  837.     },
  838.  
  839.     getRowIndex : function(rows, target)
  840.     {
  841.         return Array.indexOf(rows, target);
  842.     },
  843.  
  844.     getAncestorRow : function(elem, useSubRow)
  845.     {
  846.         return getAncestorByClass(elem, useSubRow ? 'focusRow' : 'outerFocusRow');
  847.     },
  848.  
  849.     onConsoleMouseDown : function(event)
  850.     {
  851.         var node = getAncestorByClass(event.target, 'focusRow');
  852.         if (node)
  853.             this.modifyPanelRow(Firebug.getElementPanel(node), node, false);
  854.         else
  855.         {
  856.             node = getAncestorByClass(event.target, 'memberRow');
  857.             if (!node)
  858.                 return;
  859.             var focusRow = node.getElementsByClassName('focusRow').item(0);
  860.             if (!focusRow)
  861.                 return
  862.             this.focusPanelRow(Firebug.getElementPanel(focusRow), focusRow);
  863.             node = getAncestorByClass(event.target, 'memberLabel')
  864.             if (!(node && hasClass(node, 'hasChildren')))
  865.                 cancelEvent(event);
  866.         }
  867.     },
  868.  
  869.     getValidRow : function(rows, index)
  870.     {
  871.         var min = 0; var max = rows.length -1;
  872.         if (index < min || index > max)
  873.             index = index < min ? 0 : max;
  874.         return rows[index];
  875.     },
  876.  
  877.     getFocusObjects : function(container)
  878.     {
  879.         var nodes = container.getElementsByClassName("a11yFocus")
  880.         return Array.filter(nodes, this.isVisibleByStyle, this);
  881.     },
  882.  
  883.     modifyConsoleRow : function(panel, row, inTabOrder)
  884.     {
  885.         if (this.isDirCell(row))
  886.             this.modifyMemberRow(panel, row, inTabOrder);
  887.         else if (this.isProfileRow(row))
  888.             this.modifyProfileRow(panel, row, inTabOrder);
  889.         else if (this.isOuterFocusRow(row, true))
  890.         {
  891.             if (hasClass(row, 'spyHeadTable') || hasClass(row, 'netInfoTabs'))
  892.                 this.modifyNetRow(panel, row, row.getAttribute('tabindex')=== '0');
  893.             else
  894.                 this.modifyLogRow(panel, row, row.getAttribute('tabindex')=== '0');
  895.         }
  896.         else return;
  897.     },
  898.  
  899.     modifyProfileRow : function(panel, row, inTabOrder)
  900.     {
  901.         var panelA11y = this.getPanelA11y(panel);
  902.         if (!panelA11y || !row)
  903.             return;
  904.             this.makeFocusable(row, inTabOrder);
  905.             var focusObjects = this.getFocusObjects(row);
  906.             Array.forEach(focusObjects, function(e,i,a) {
  907.                 this.makeFocusable(e);
  908.                 if (hasClass(e.parentNode, "profileCell"))
  909.                     e.setAttribute("role", "gridcell");
  910.             }, this);
  911.     },
  912.  
  913.     onConsoleSearchMatchFound : function(panel, text, matches)
  914.     {
  915.         var panelA11y = this.getPanelA11y(panel);
  916.         if (!panelA11y)
  917.             return;
  918.         var matchFeedback = "";
  919.         if (!matches || matches.length == 0)
  920.             matchFeedback = $STRF('a11y.updates.no matches found', [text]);
  921.         else
  922.             matchFeedback = $STRF('a11y.updates.match found in logrows', [text, matches.length]);
  923.         this.updateLiveElem(panel, matchFeedback, true); //should not use alert
  924.     },
  925.  
  926.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  927.     // HTML Panel
  928.  
  929.     onHTMLKeyPress: function(event)
  930.     {
  931.         var target = event.target;
  932.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  933.         if ([13, 32, KeyEvent.DOM_VK_F2].indexOf(keyCode) == -1)
  934.             return;
  935.         if (!hasClass(target, "nodeLabelBox"))
  936.             return;
  937.         var panel = Firebug.getElementPanel(target);
  938.         switch(keyCode)
  939.         {
  940.             case 13:
  941.             case 32:
  942.                 var isEnter = keyCode == 13;
  943.                 var nodeLabels = null;
  944.                 if (isEnter)
  945.                 {
  946.                     var nodeLabels = target.getElementsByClassName('nodeName');
  947.                     if (nodeLabels.length > 0)
  948.                     {
  949.                         Firebug.Editor.startEditing(nodeLabels[0]);
  950.                         cancelEvent(event);
  951.                     }
  952.                 }
  953.                 if (!isEnter || nodeLabels.length == 0)
  954.                 {
  955.                     var nodeBox = getAncestorByClass(target, 'nodeBox');
  956.                     if (nodeBox.repObject && panel.editNewAttribute)
  957.                     {
  958.                         panel.editNewAttribute(nodeBox.repObject)
  959.                         cancelEvent(event);
  960.                     }
  961.                 }
  962.                 break;
  963.             case KeyEvent.DOM_VK_F2:
  964.                 if (hasClass(target.parentNode.parentNode, 'textNodeBox'))
  965.                 {
  966.                     var textNode = getChildByClass(target, 'nodeText');
  967.                     if (textNode)
  968.                         Firebug.Editor.startEditing(textNode);
  969.                 }
  970.                 break;
  971.         }
  972.     },
  973.  
  974.     onHTMLFocus : function(event)
  975.     {
  976.         if (hasClass(event.target, 'nodeLabelBox'))
  977.         {
  978.             this.dispatchMouseEvent(event.target, 'mouseover');
  979.             var nodeLabel = getAncestorByClass(event.target, 'nodeLabel');
  980.             if (nodeLabel)
  981.                 setClass(nodeLabel, 'focused');
  982.             cancelEvent(event);
  983.         }
  984.     },
  985.  
  986.     onHTMLBlur : function(event)
  987.     {
  988.         if (hasClass(event.target, 'nodeLabelBox'))
  989.         {
  990.             this.dispatchMouseEvent(event.target, 'mouseout');
  991.             var nodeLabel = getAncestorByClass(event.target, 'nodeLabel');
  992.             if (nodeLabel)
  993.                 removeClass(nodeLabel, 'focused');
  994.             cancelEvent(event);
  995.         }
  996.     },
  997.  
  998.     onObjectBoxSelected: function(objectBox)
  999.     {
  1000.         var panel = Firebug.getElementPanel(objectBox);
  1001.         var panelA11y = this.getPanelA11y(panel);
  1002.         if (!panelA11y)
  1003.             return;
  1004.         var label = objectBox.firstChild.getElementsByClassName('nodeLabelBox').item(0);
  1005.         if (label) {
  1006.             this.makeFocusable(label, true);
  1007.             if (this.panelHasFocus(panel))
  1008.                 this.focus(label);
  1009.         }
  1010.     },
  1011.  
  1012.     onObjectBoxUnselected: function(objectBox)
  1013.     {
  1014.         if (!this.isEnabled() ||  !objectBox)
  1015.             return;
  1016.         var label = objectBox.firstChild.getElementsByClassName('nodeLabelBox').item(0);
  1017.         if (label) {
  1018.             this.makeUnfocusable(label, true);
  1019.         }
  1020.     },
  1021.  
  1022.     onHTMLSearchMatchFound: function(panel, match)
  1023.     {
  1024.         var panelA11y = this.getPanelA11y(panel);
  1025.         if (!panelA11y)
  1026.             return;
  1027.         var node = match.node;
  1028.         var elem;
  1029.         var matchFeedback = ""; 
  1030.         switch (node.nodeType)
  1031.         {
  1032.             case 1: //element
  1033.                 elem = node;
  1034.                 matchFeedback += $STRF("a11y.updates.match found in element", [match.match[0], elem.nodeName, getElementTreeXPath(elem)]);
  1035.                 break;
  1036.             case 2: //attribute
  1037.                 elem = node.ownerElement;
  1038.                 matchFeedback += $STRF("a11y.updates.match found in attribute", 
  1039.                     [match.match[0], node.nodeName, node.nodeValue, elem.nodeName, getElementTreeXPath(elem)]);   
  1040.                 break;
  1041.                 
  1042.             case 3: //text content
  1043.                 elem = node.parentNode;
  1044.                 matchFeedback += $STRF("a11y.updates.match found in text content", [match.match[0], match.match.input]); 
  1045.                 break;
  1046.         }
  1047.         this.updateLiveElem(panel, matchFeedback, true); //should not use alert
  1048.     },
  1049.  
  1050.     onHTMLSearchNoMatchFound: function(panel, text)
  1051.     {
  1052.         this.updateLiveElem(panel, $STRF('a11y.updates.no matches found', [text]), true); //should not use alert
  1053.     },
  1054.  
  1055.     moveToSearchMatch: function()
  1056.     {
  1057.         if (!this.isEnabled())
  1058.             return;
  1059.         var panel = Firebug.chrome.getSelectedPanel();
  1060.         var panelA11y = this.getPanelA11y(panel);
  1061.         if (!panelA11y || !panel.searchable )
  1062.             return;
  1063.         var popup = Firebug.chrome.$('fbSearchOptionsPopup');
  1064.         if (popup)
  1065.             popup.hidePopup();
  1066.         switch(panel.name)
  1067.         {
  1068.             case 'html':
  1069.                 var match = panel.lastSearch.lastMatch;
  1070.                 if (!match)
  1071.                     return;
  1072.                 var nodeBox = panel.lastSearch.openToNode(match.node, match.isValue);
  1073.                 if (!nodeBox)
  1074.                     return;
  1075.                 nodeBox = getAncestorByClass(nodeBox, 'nodeBox');
  1076.                 panel.select(nodeBox.repObject, true);
  1077.                 break;
  1078.             case 'stylesheet':
  1079.                 if (panel.currentSearch && panel.currentSearch.currentNode)
  1080.                 {
  1081.                     var focusRow = getAncestorByClass(panel.currentSearch.currentNode, 'focusRow');
  1082.                     if (focusRow)
  1083.                     {
  1084.                         this.focusPanelRow(panel, focusRow);
  1085.                     }
  1086.                 }
  1087.                 break
  1088.             case 'script' :
  1089.                 if (panel.currentSearch && panel.selectedSourceBox)
  1090.                 {
  1091.                     var box = panel.selectedSourceBox;
  1092.                     var lineNo = panel.currentSearch.mark;
  1093.                     box.a11yCaretLine = lineNo + 1;
  1094.                     box.a11yCaretOffset = 0;
  1095.                     panel.scrollToLine(box.repObject.href, lineNo, panel.jumpHighlightFactory(lineNo+1, panel.context));
  1096.                     var viewport = box.getElementsByClassName('sourceViewport').item(0);
  1097.                     if(viewport)
  1098.                     {
  1099.                         this.focus(viewport);
  1100.                         this.insertCaretIntoLine(panel, box);
  1101.                     }
  1102.                 }
  1103.                 break;
  1104.             case 'dom':
  1105.                 if (panel.currentSearch && panel.currentSearch.currentNode)
  1106.                 {
  1107.                     var focusRow = panel.currentSearch.currentNode.getElementsByClassName('focusRow').item(0);
  1108.                     if (focusRow)
  1109.                     {
  1110.                         this.focusPanelRow(panel, focusRow);
  1111.                     }
  1112.                 }
  1113.                 break;
  1114.             case 'net':
  1115.                 if (panel.currentSearch && panel.currentSearch.currentNode)
  1116.                 {
  1117.                     var focusRow = getAncestorByClass(panel.currentSearch.currentNode, 'focusRow');
  1118.                     if (focusRow)
  1119.                     {
  1120.                         this.focusPanelRow(panel, focusRow);
  1121.                     }
  1122.                 }
  1123.                 break;
  1124.         }
  1125.     },
  1126.  
  1127.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1128.     // CSS Panel
  1129.  
  1130.     onCSSKeyPress : function(event)
  1131.     {
  1132.         var target = event.target;
  1133.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  1134.         if (!this.isFocusRow(target))
  1135.             return;
  1136.         else if (event.altKey)
  1137.             return;
  1138.         else if ([13, 32, 33, 34, 35, 36, 38,  40].indexOf(keyCode) == -1)
  1139.             return;//not interested in any other keys, than arrows, pg, home/end, space & enter
  1140.         var panel = Firebug.getElementPanel(target)
  1141.         var panelA11y = this.getPanelA11y(panel);
  1142.         if (!panelA11y)
  1143.             return;
  1144.         switch (keyCode)
  1145.         {
  1146.             case 38://up
  1147.             case 40://down
  1148.                 var goUp = keyCode == 38;
  1149.                 if (event.ctrlKey)
  1150.                 {
  1151.                     if (event.shiftKey)
  1152.                     {
  1153.                         var node = this[goUp ? 'getPreviousByClass' : 'getNextByClass'](target, 'cssInheritHeader', panel.panelNode);
  1154.                         if (node)
  1155.                         {
  1156.                             this.focusPanelRow(panel, node);
  1157.                         }
  1158.                         else if (goUp)
  1159.                            this.focusEdgeCSSRow(panel, target, true);
  1160.                     }
  1161.                     else
  1162.                         this.focusSiblingHeadRow(panel, target, goUp);
  1163.                 }
  1164.                 else
  1165.                     this.focusSiblingCSSRow(panel, target, goUp);
  1166.                 break;
  1167.             case 35://end
  1168.             case 36://home
  1169.                 if (event.ctrlKey)
  1170.                     this.focusEdgeHeadRow(panel, target, keyCode == 36);
  1171.                 else
  1172.                     this.focusEdgeCSSRow(panel, target, keyCode == 36);
  1173.                 break;
  1174.             case 33://pgup
  1175.             case 34://pgdn
  1176.                 if (event.ctrlKey)
  1177.                     this.focusPageSiblingHeadRow(panel, target, keyCode == 33);
  1178.                 else
  1179.                     this.focusPageSiblingCSSRow(panel, target, keyCode == 33);
  1180.                 break;
  1181.             case 13://enter
  1182.                 if (hasClass(target, 'cssProp'))
  1183.                 {
  1184.                     var node = getChildByClass(target, 'cssPropName');
  1185.                     if (node)
  1186.                         Firebug.Editor.startEditing(node);
  1187.                     cancelEvent(event);
  1188.                 }
  1189.                 else if (hasClass(target, 'cssHead'))
  1190.                 {
  1191.                     var node = getChildByClass(target, 'cssSelector');
  1192.                     if (node && hasClass(node, 'editable'))
  1193.                         Firebug.Editor.startEditing(node);
  1194.                     cancelEvent(event);
  1195.                 }
  1196.                 else if (hasClass(target, 'importRule'))
  1197.                 {
  1198.                     var node = getChildByClass(target, 'objectLink');
  1199.                     if (node)
  1200.                         this.dispatchMouseEvent(node, 'click');
  1201.                 }
  1202.                 break;
  1203.             case 32://space
  1204.                 if (hasClass(target, 'cssProp'))
  1205.                 {
  1206.                     //our focus is about to be wiped out, we'll try to get it back after
  1207.                     panelA11y.reFocusId = getElementXPath(target);
  1208.                     panel.disablePropertyRow(target);
  1209.                     if (panel.name == "stylesheet")
  1210.                         target.setAttribute('aria-checked', !hasClass(target, 'disabledStyle'));
  1211.                     cancelEvent(event);
  1212.                 }
  1213.                 break;
  1214.         }
  1215.         if (!event.shiftKey)
  1216.             event.preventDefault();
  1217.     },
  1218.  
  1219.     onCSSMouseDown : function(event)
  1220.     {
  1221.         var row = getAncestorByClass(event.target, 'focusRow');
  1222.         if (row)
  1223.             this.modifyPanelRow(Firebug.getElementPanel(row), row, false);
  1224.     },
  1225.  
  1226.     focusSiblingCSSRow : function(panel, target, goUp)
  1227.     {
  1228.         var newRow = this[goUp ? 'getPreviousByClass' : 'getNextByClass'](target, 'focusRow', panel.panelNode)
  1229.         if (!newRow)
  1230.             return;
  1231.         this.focusPanelRow(panel, newRow, false);
  1232.     },
  1233.  
  1234.     focusPageSiblingCSSRow : function(panel, target, goUp)
  1235.     {
  1236.         var rows = this.getFocusRows(panel);
  1237.         var index = this.getRowIndex(rows, target);
  1238.         var newRow = this.getValidRow(rows, goUp ? index - 10 : index + 10);
  1239.         this.focusPanelRow(panel, newRow, false);
  1240.     },
  1241.  
  1242.     focusEdgeCSSRow : function(panel, target, goUp)
  1243.     {
  1244.         var rows = this.getFocusRows(panel);
  1245.         var newRow = this.getValidRow(rows, goUp ? 0 : rows.length -1);
  1246.         this.focusPanelRow(panel, newRow, false);
  1247.     },
  1248.  
  1249.     getHeadRowsAndIndex: function(panel, elem)
  1250.     {
  1251.         var rows = this.getFocusRows(panel);
  1252.         var headRow = hasClass(elem, 'cssHead') ? elem : getPreviousByClass(elem, 'cssHead');
  1253.         var headRows = Array.filter(rows, function(e,i,a){return hasClass(e, 'cssHead')});
  1254.         var index = Array.indexOf(headRows, headRow);
  1255.         if (index == -1)
  1256.             index = 0;
  1257.         return [headRows, index]
  1258.     },
  1259.  
  1260.     focusSiblingHeadRow : function(panel, elem, goUp)
  1261.     {
  1262.         var rowInfo = this.getHeadRowsAndIndex(panel, elem);
  1263.         var newRow = this.getValidRow(rowInfo[0], goUp ? rowInfo[1] - 1 : rowInfo[1] + 1);
  1264.         this.focusPanelRow(panel, newRow, false);
  1265.     },
  1266.  
  1267.     focusPageSiblingHeadRow : function(panel, elem, goUp)
  1268.     {
  1269.         var rowInfo = this.getHeadRowsAndIndex(panel, elem);
  1270.         var newRow = this.getValidRow(rowInfo[0], goUp ? rowInfo[1] - 10 : rowInfo[1] + 10);
  1271.         this.focusPanelRow(panel, newRow, false);
  1272.     },
  1273.  
  1274.     focusEdgeHeadRow : function(panel, elem, goUp)
  1275.     {
  1276.         var rowInfo = this.getHeadRowsAndIndex(panel, elem);
  1277.         var newRow = this.getValidRow(rowInfo[0], goUp ? 0 : rowInfo[0].length - 1);
  1278.         this.focusPanelRow(panel, newRow, false);
  1279.     },
  1280.  
  1281.     onBeforeCSSRulesAdded : function(panel)
  1282.     {
  1283.         // Panel content is about to be recreated, possibly wiping out focus.
  1284.         // Use the focused element's xpath to remember which rule had focus so that it can be refocused when the panel content is drawn again
  1285.         var panelA11y = this.getPanelA11y(panel);
  1286.         if (!panelA11y || !this.panelHasFocus(panel))
  1287.             return;
  1288.         if (panelA11y.tabStop && hasClass(panelA11y.tabStop, 'focusRow'))
  1289.             panelA11y.reFocusId = getElementXPath(panelA11y.tabStop);
  1290.     },
  1291.  
  1292.     onCSSRulesAdded : function(panel, rootNode)
  1293.     {
  1294.         var panelA11y = this.getPanelA11y(panel);
  1295.         if (!panelA11y)
  1296.             return;
  1297.         var row;
  1298.         if (panelA11y.reFocusId)
  1299.         {   //we need to put focus back to where it was before it was wiped out
  1300.             var reFocusRows = getElementsByXPath(rootNode.ownerDocument, panelA11y.reFocusId);
  1301.             panelA11y.reFocusId = null;
  1302.             if (reFocusRows.length > 0)
  1303.             {
  1304.                 row = reFocusRows[0];
  1305.                 this.modifyPanelRow(panel, row, true);
  1306.                 this.focus(row, true);
  1307.                 this.setPanelTabStop(panel, row);
  1308.                 return;
  1309.             }
  1310.         }
  1311.         //no refocus needed, just make first rule the panel's tab stop
  1312.         row = rootNode.getElementsByClassName('focusRow').item(0);
  1313.         this.modifyPanelRow(panel, row, true);
  1314.         return;
  1315.     },
  1316.     //applies a11y changes (keyboard and screen reader related) to an individual row
  1317.     //To improve performance, this only happens when absolutely necessary, e.g. when the user navigates to the row in question
  1318.  
  1319.     modifyCSSRow : function(panel, row, inTabOrder)
  1320.     {
  1321.         if (!panel || !row)
  1322.             return;
  1323.         var rule = getAncestorByClass(row, "cssRule");
  1324.         if (inTabOrder)
  1325.             this.setPanelTabStop(panel, row);
  1326.         else
  1327.             this.makeFocusable(row);
  1328.         if (rule && !hasClass(rule, 'a11yModified'))
  1329.         {
  1330.             var listBox = rule.getElementsByClassName('cssPropertyListBox').item(0);
  1331.             var selector = rule.getElementsByClassName('cssSelector').item(0);
  1332.             if (listBox && selector)
  1333.                 listBox.setAttribute('aria-label', $STRF("a11y.labels.declarations for selector", [selector.textContent]));
  1334.             setClass(rule, 'a11yModified')
  1335.         }
  1336.         if (hasClass(row, 'cssHead'))
  1337.         {
  1338.             if (panel.name == "css")
  1339.             {
  1340.                 var sourceLink = rule.parentNode.lastChild;
  1341.                 if (sourceLink && hasClass(sourceLink, "objectLink"))
  1342.                     row.setAttribute('aria-label', row.textContent + " " + $STRF('a11y.labels.defined in file', [sourceLink.textContent]));
  1343.             }
  1344.         }
  1345.         else if (hasClass(row, 'cssProp'))
  1346.         {
  1347.             row.setAttribute('aria-checked', !hasClass(row, 'disabledStyle'));
  1348.             if (hasClass(row, 'cssOverridden'))
  1349.                 row.setAttribute('aria-label', $STR('aria.labels.overridden') + " " + row.textContent);
  1350.         }
  1351.         return;
  1352.     },
  1353.  
  1354.     onCSSPanelContextMenu : function(event)
  1355.     {
  1356.         var panelA11y = this.getPanelA11y(panel);
  1357.         if (!panelA11y)
  1358.             return;
  1359.         if (event.button == 0) //the event was created by keyboard, not right mouse click
  1360.         {
  1361.             var panel = Firebug.getElementPanel(event.target);
  1362.             if (panel && hasClass(event.target, 'focusRow'))
  1363.             {
  1364.                 var node = event.target;
  1365.                 if (panel.name == "css")
  1366.                 {
  1367.                     if (hasClass(event.target, 'cssHead'))
  1368.                         node = event.target.parentNode.getElementsByClassName('objectLink').item(0);
  1369.                     else if (hasClass(event.target, 'cssInheritHeader'))
  1370.                         node = event.target.getElementsByClassName('objectLink').item(0);
  1371.                     if (!node || hasClass(node, 'collapsed'))
  1372.                         node = event.target;
  1373.                 }
  1374.                 //these context menu options are likely to destroy current focus
  1375.                 panelA11y.reFocusId = getElementXPath(event.target);
  1376.                 document.popupNode = node;
  1377.                 Firebug.chrome.$('fbContextMenu').openPopup(node, 'overlap', 0,0,true);
  1378.                 cancelEvent(event); //no need for default handlers anymore
  1379.             }
  1380.         }
  1381.     },
  1382.  
  1383.     onCSSSearchMatchFound : function(panel, text, matchRow)
  1384.     {
  1385.         var panelA11y = this.getPanelA11y(panel);
  1386.         if (!panelA11y || !text)
  1387.             return;
  1388.         if (!matchRow)
  1389.         {
  1390.             this.updateLiveElem(panel, $STRF('a11y.updates.no matches found', [text]), true); //should not use alert
  1391.             return;
  1392.         }
  1393.         var matchFeedback = "";
  1394.         var matchType = '';
  1395.         var selector;
  1396.         if (hasClass(matchRow, 'cssSelector'))
  1397.             matchFeedback = " " + $STRF('a11y.updates.match found in selector', [text, matchRow.textContent]);
  1398.         else
  1399.         {
  1400.             selector = getPreviousByClass(matchRow, 'cssSelector');
  1401.             selector = selector ? selector.textContent : "";
  1402.             if (hasClass(matchRow, 'cssPropName') || hasClass(matchRow, 'cssPropValue'))
  1403.             {
  1404.                 var propRow = getAncestorByClass(matchRow, 'cssProp');
  1405.                 if (propRow)
  1406.                     matchFeedback = $STRF('a11y.updates.match found in style declaration', [text, propRow.textContent, selector]);
  1407.             }
  1408.         }
  1409.         this.updateLiveElem(panel, matchFeedback, true); // should not use alert
  1410.     },
  1411.  
  1412.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1413.     // Layout Panel
  1414.  
  1415.     onLayoutBoxCreated : function(panel, node, detailsObj)
  1416.     {
  1417.         var panelA11y = this.getPanelA11y(panel);
  1418.         if (!panelA11y)
  1419.             return;
  1420.         var focusGroups = node.getElementsByClassName('focusGroup');
  1421.         Array.forEach(focusGroups, function(e,i,a){
  1422.             if (hasClass(e, 'positionLayoutBox'))
  1423.                 this.makeFocusable(e, true);
  1424.             else
  1425.                 this.makeFocusable(e, false);
  1426.             e.setAttribute('role', 'group');
  1427.             e.setAttribute('aria-label', this.getLayoutBoxLabel(e, detailsObj));
  1428.             e.setAttribute('aria-setsize', a.length);
  1429.             e.setAttribute('aria-posinset', i + 1);
  1430.         }, this);
  1431.     },
  1432.  
  1433.     getLayoutBoxLabel : function(elem, detailsObj )
  1434.     {
  1435.         var className = elem.className.match(/\b(\w+)LayoutBox\b/);
  1436.         if (!className)
  1437.             return "";
  1438.         var styleName = className[1];
  1439.         var output = "";
  1440.         switch(styleName)
  1441.         {
  1442.             case "position":
  1443.                 output += hasClass(elem, "blankEdge") ? '' : $STR("a11y.layout.position");
  1444.                 styleName = "outer";
  1445.                 break;
  1446.             case "margin":
  1447.                 output += $STR("a11y.layout.margin");
  1448.                 break;
  1449.             case "border":
  1450.                 output += $STR("a11y.layout.border");
  1451.                 break;
  1452.             case "padding":
  1453.                 output += $STR("a11y.layout.padding");
  1454.                 break;
  1455.             case "content":
  1456.                 output += $STR("a11y.layout.size");
  1457.                 break;
  1458.         }
  1459.         output += ": ";
  1460.         var valNames = [];
  1461.         var vals = {};
  1462.         switch (styleName)
  1463.         {
  1464.             case "outer":        
  1465.                 valNames = ['top', 'left', "position", "z-index"];
  1466.                 vals.top = detailsObj[styleName + 'Top'];
  1467.                 vals.left = detailsObj[styleName + 'Left'];
  1468.                 vals.position = detailsObj.position;
  1469.                 vals["z-index"] = detailsObj.zIndex;
  1470.                 break;
  1471.             case "content":
  1472.                 valNames = ['width', 'height']
  1473.                 vals.width = detailsObj['width'];
  1474.                 vals.height = detailsObj['height'];
  1475.                 break;
  1476.             default:
  1477.                 valNames = ['top', 'right', 'bottom', 'left'];
  1478.                 vals.top = detailsObj[styleName + 'Top'];
  1479.                 vals.right = detailsObj[styleName + 'Right'];
  1480.                 vals.bottom = detailsObj[styleName + 'Bottom'];
  1481.                 vals.left = detailsObj[styleName + 'Left'];
  1482.                 break;
  1483.         }
  1484.         
  1485.         for (var i = 0 ; i < valNames.length; i++)
  1486.         {
  1487.             output += $STR("a11y.layout." + valNames[i]) + " = " + vals[valNames[i]];
  1488.             output += i == valNames.length -1 ? "" : ", ";
  1489.         }
  1490.         return output;
  1491.     },
  1492.  
  1493.     onLayoutKeyPress : function(event)
  1494.     {
  1495.         var target = event.target;
  1496.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  1497.         if ([13, 37, 38, 39, 40].indexOf(keyCode) == -1)
  1498.             return;
  1499.         if (!hasClass(target, 'focusGroup'))
  1500.             return;
  1501.         var panel = Firebug.getElementPanel(target);
  1502.         switch(keyCode)
  1503.         {
  1504.             case 37:
  1505.             case 38:
  1506.             case 39:
  1507.             case 40:
  1508.                 var node, goLeft = keyCode == 37 || keyCode == 38;
  1509.                 if (goLeft)
  1510.                     node = getAncestorByClass(target.parentNode, 'focusGroup');
  1511.                 else
  1512.                     node = getChildByClass(target, 'focusGroup');
  1513.                 if (node)
  1514.                     this.focus(node);
  1515.                 break;
  1516.             case 13:
  1517.                 var editable = target.getElementsByClassName('editable').item(0);
  1518.                 if (editable)
  1519.                     Firebug.Editor.startEditing(editable);
  1520.                 cancelEvent(event);
  1521.                 break;
  1522.         }
  1523.     },
  1524.  
  1525.     onLayoutFocus : function(event)
  1526.     {
  1527.         if (hasClass(event.target, 'focusGroup'))
  1528.         {
  1529.             this.dispatchMouseEvent(event.target, 'mouseover');
  1530.             this.setPanelTabStop(Firebug.getElementPanel(event.target), event.target);
  1531.         }
  1532.     },
  1533.  
  1534.     onLayoutBlur : function(event)
  1535.     {
  1536.         if (hasClass(event.target, 'focusGroup'))
  1537.             this.dispatchMouseEvent(event.target, 'mouseout');
  1538.     },
  1539.  
  1540.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1541.     // Inline Editing
  1542.     onInlineEditorShow : function(panel, editor)
  1543.     {
  1544.         var panelA11y = this.getPanelA11y(panel);
  1545.         if (!panelA11y)
  1546.             return;
  1547.         //recreate the input element rather than reusing the old one, otherwise AT won't pick it up
  1548.         editor.input.onkeypress = editor.input.oninput = editor.input.onoverflow = null;
  1549.         editor.inputTag.replace({}, editor.box.childNodes[1].firstChild, editor);
  1550.         editor.input = editor.box.childNodes[1].firstChild.firstChild;
  1551.     },
  1552.  
  1553.     onBeginEditing : function(panel, editor, target, value)
  1554.     {
  1555.         switch (panel.name)
  1556.         {
  1557.             case 'html':
  1558.                 var tagName= nodeName = null;
  1559.                 var setSize = posInSet = 0; var setElems;
  1560.                 var label = $STR("a11y.labels.inline editor") + ": ";
  1561.                 if (hasClass(target, 'nodeName') || hasClass(target, 'nodeValue'))
  1562.                 {
  1563.                     var isName = hasClass(target, 'nodeName');
  1564.                     setElems = target.parentNode.parentNode.getElementsByClassName(isName ? 'nodeName' : 'nodeValue');
  1565.                     setSize = (setElems.length * 2);
  1566.                     posInSet = ((Array.indexOf(setElems, target) + 1) * 2) - (isName ? 1 : 0);
  1567.                     editor.input.setAttribute('role', 'listitem');
  1568.                     editor.input.setAttribute('aria-setsize', setSize);
  1569.                     editor.input.setAttribute('aria-posinset', posInSet);
  1570.                     nodeTag = getPreviousByClass(target, 'nodeTag');
  1571.                     if (!isName)
  1572.                     {
  1573.                         nodeName = getPreviousByClass(target, 'nodeName');
  1574.                         label += $STRF('a11y.labels.value for attribute in element', [nodeName.textContent, nodeTag.textContent]);
  1575.                     }
  1576.                     else
  1577.                         label += $STRF("a11y.label.attribute for element", [nodeTag.textContent]);
  1578.                 }
  1579.                 else if (hasClass(target, 'nodeText'))
  1580.                 {
  1581.                     nodeTag = getPreviousByClass(target, 'nodeTag');
  1582.                     label += $STRF("a11y.labels.text contents for element", [nodeTag.textContent]);
  1583.                 }
  1584.                 editor.input.setAttribute('aria-label', label);
  1585.                 break;
  1586.             case 'css':
  1587.             case 'stylesheet':
  1588.                 var selector = getPreviousByClass(target, 'cssSelector');
  1589.                 selector = selector ? selector.textContent : "";
  1590.                 var label = $STR("a11y.labels.inline editor") + ": ";
  1591.                 if (hasClass(target, 'cssPropName'))
  1592.                     label += $STRF('a11y.labels.property for selector', [selector]);
  1593.                 else if (hasClass(target, 'cssPropValue'))
  1594.                 {
  1595.                     var propName = getPreviousByClass(target, 'cssPropName');
  1596.                     propName = propName ? propName.textContent : "";
  1597.                     label += $STRF('a11y.labels.value property in selector', [propName, selector]);
  1598.                 }
  1599.                 else if (hasClass(target, 'cssSelector'))
  1600.                     label += $STR('a11y.labels.css selector');
  1601.                 editor.input.setAttribute('aria-label', label);
  1602.                 editor.setAttribute('aria-autocomplete', 'inline');
  1603.                 break;
  1604.             case 'layout':
  1605.                 editor.input.setAttribute('aria-label', target.getAttribute('aria-label'));
  1606.                 break;
  1607.             case 'dom':
  1608.             case 'domSide':
  1609.                 if (target.cells && target.cells[1])
  1610.                     editor.input.setAttribute('aria-label', target.cells[1].textContent);
  1611.                 break;
  1612.         }
  1613.     },
  1614.  
  1615.     onInlineEditorClose  : function(panel, target, removeGroup)
  1616.     {
  1617.         var panelA11y = this.getPanelA11y(panel);
  1618.         if (!panelA11y)
  1619.             return;
  1620.         switch (panel.name)
  1621.         {
  1622.             case 'layout':
  1623.                 var box = getAncestorByClass(target, 'focusGroup')
  1624.                 if (box)
  1625.                     this.focus(box, true);
  1626.                 break;
  1627.             case 'css':
  1628.             case 'stylesheet':
  1629.                 var node = target.parentNode;
  1630.                 if (removeGroup)
  1631.                     node = this.getPreviousByClass(node, 'focusRow', panel.panelNode);
  1632.                 if (node)
  1633.                 {
  1634.                     this.focusPanelRow(panel, node, true);
  1635.                 }
  1636.                 break;
  1637.             case 'html':
  1638.                 var box = getAncestorByClass(target, 'nodeBox')
  1639.                 if (box)
  1640.                     panel.select(box.repObject, true);
  1641.                 break;
  1642.             case 'watches':
  1643.                 var node = target.getElementsByClassName('watchEditBox').item(0);
  1644.                 if (node)
  1645.                     this.focus(node, true);
  1646.                 break;
  1647.             case 'script':
  1648.                 panel.selectedSourceBox.focus();
  1649.                 break;
  1650.         }
  1651.     },
  1652.  
  1653.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1654.     // Script Panel
  1655.  
  1656.     onStop : function(context, frame, type,rv)
  1657.     {
  1658.         if (!context)
  1659.             return;
  1660.         var panel = context.getPanel('script');
  1661.         var panelA11y = this.getPanelA11y(panel);
  1662.         if (!panelA11y)
  1663.             return;
  1664.         var fileName =  frame.script.fileName.split("/");
  1665.         fileName = fileName.pop();
  1666.         var alertString = $STRF("a11y.updates.script_suspended_on_line_in_file",[frame.line, frame.functionName, fileName]);
  1667.         this.updateLiveElem(panel, alertString, true);
  1668.         this.onShowSourceLink(panel, frame.line);
  1669.     },
  1670.  
  1671.     onShowSourceLink : function (panel, lineNo)
  1672.     {
  1673.         if (!this.isEnabled())
  1674.             return;
  1675.         var box = panel.selectedSourceBox;
  1676.         var viewport = box.getElementsByClassName('sourceViewport').item(0);
  1677.         box.a11yCaretLine = lineNo;
  1678.         if (viewport && this.panelHasFocus(panel))
  1679.         {
  1680.             this.focus(viewport);
  1681.             this.insertCaretIntoLine(panel, box, lineNo);
  1682.         }
  1683.     },
  1684.  
  1685.     onScriptKeyPress : function(event)
  1686.     {
  1687.         var target = event.target;
  1688.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  1689.         if (!hasClass(target, 'sourceViewport'))
  1690.             return;
  1691.       if ([13, 33, 34, 35, 36, 37, 38, 39, 40].indexOf(keyCode) == -1)
  1692.          return;
  1693.         var panel = Firebug.getElementPanel(target);
  1694.         var panelA11y = this.getPanelA11y(panel);
  1695.         if (!panelA11y)
  1696.             return;
  1697.         var box = panel.selectedSourceBox
  1698.         var lastLineNo = box.lastViewableLine;
  1699.         var firstLineNo = box.firstViewableLine;
  1700.         var caretDetails = this.getCaretDetails(event.target.ownerDocument);
  1701.         if (!caretDetails || caretDetails.length != 2)
  1702.             return;
  1703.         var lineNode = getAncestorByClass(caretDetails[0].parentNode, 'sourceRow');
  1704.         if (!lineNode )
  1705.             return;
  1706.         var lineNo = parseInt(lineNode.getElementsByClassName('sourceLine').item(0).textContent);
  1707.         box.a11yCaretLine = lineNo;
  1708.         box.a11yCaretOffset = caretDetails[1];
  1709.         var newLineNo = 1;
  1710.         var linesToScroll = 0;
  1711.         var goUp;
  1712.         switch(keyCode)
  1713.         {
  1714.             case 38:
  1715.             case 40:
  1716.                 goUp = keyCode == 38
  1717.                 linesToScroll = goUp ? -1 : 1;
  1718.                 if (!event.ctrlKey)
  1719.                 {
  1720.                     if (goUp && (lineNo > (firstLineNo +  1)))
  1721.                         return;
  1722.                     else if (!goUp && (lineNo < (lastLineNo -  1)))
  1723.                         return;
  1724.                     box.a11yCaretLine = goUp ? lineNo - 1 : lineNo +1;
  1725.                 }
  1726.                 box.scrollTop = box.scrollTop + (linesToScroll * box.lineHeight);
  1727.                 break;
  1728.             case 33://pgup
  1729.             case 34://pgdn
  1730.                 goUp = keyCode == 33;
  1731.                 if ((goUp && box.scrollTop == 0) || (!goUp && box.scrollTop == box.scrollHeight - box.clientHeight))
  1732.                 {
  1733.                     box.a11yCaretLine = goUp ? 0 : box.totalMax;
  1734.                     box.a11yCaretOffset = 0;
  1735.                     this.insertCaretIntoLine(panel, box);
  1736.                     cancelEvent(event);
  1737.                     return
  1738.                 }
  1739.                 box.a11yCaretLine = goUp ? lineNo - box.viewableLines : lineNo + box.viewableLines;
  1740.                 linesToScroll = goUp ? -box.viewableLines : box.viewableLines;
  1741.                 box.scrollTop = box.scrollTop + (linesToScroll * box.lineHeight);
  1742.                 cancelEvent(event);
  1743.                 break;
  1744.             case 36://home
  1745.             case 35://end
  1746.                 goUp = keyCode == 36;
  1747.                 if (event.ctrlKey)
  1748.                 {
  1749.                     box.a11yCaretLine = goUp ? 0 : box.totalMax;
  1750.                     box.a11yCaretOffset = 0;
  1751.                     if ((goUp && box.scrollTop == 0) || (!goUp && box.scrollTop == box.scrollHeight - box.clientHeight))
  1752.                         this.insertCaretIntoLine(panel, box);
  1753.                     else
  1754.                         box.scrollTop = goUp ? 0 : box.scrollHeight - box.clientHeight;;
  1755.                     cancelEvent(event);
  1756.                     return;
  1757.                 }
  1758.                 if (goUp)
  1759.                 {
  1760.                     //move caret to beginning of line. Override default behavior, as that would take the caret into the line number
  1761.                     this.insertCaretIntoLine(panel, box, lineNo, 0);
  1762.                     box.scrollLeft = 0; //in case beginning of line is scrolled out of view
  1763.                     cancelEvent(event);
  1764.                 }
  1765.                 break;
  1766.             case 13:
  1767.                 var liveString = "";
  1768.                 var caretDetails = this.getCaretDetails(event.target.ownerDocument);
  1769.                 var lineNode = getAncestorByClass(caretDetails[0].parentNode, 'sourceRow');
  1770.                 var lineNo = parseInt(lineNode.getElementsByClassName('sourceLine').item(0).textContent);
  1771.                 liveString += "Line " + lineNo;
  1772.                 if (lineNode.getAttribute('breakpoint') == 'true')
  1773.                 {
  1774.                     var breakpointStr;
  1775.                     if (lineNode.getAttribute('disabledbreakpoint') == 'true')
  1776.                         breakpointStr = "a11y.updates.hasdisabled breakpoint";
  1777.                     if (lineNode.getAttribute('condition') == 'true')
  1778.                         breakpointStr = "a11y.updates.has conditional breakpoint";
  1779.                     liveString += ", " + $STR(breakpointStr);
  1780.                 }
  1781.                 if (lineNode.getAttribute('executable') == 'true')
  1782.                     liveString += ", executable";
  1783.                 if (lineNode.getAttribute('exe_line') == 'true')
  1784.                     liveString += ", currently stopped";
  1785.                 var sourceText = lineNode.getElementsByClassName('sourceRowText').item(0);
  1786.                 if (sourceText)
  1787.                     liveString += ": " + sourceText.textContent;
  1788.                 this.updateLiveElem(panel, liveString, true); //should not use alert
  1789.                 break
  1790.         }
  1791.     },
  1792.  
  1793.     onScriptKeyUp : function(event)
  1794.     {
  1795.         var target = event.target;
  1796.         var keyCode = event.keyCode || (event.type=='keypress' ? event.charCode : null);
  1797.         if (!hasClass(target, 'sourceViewport'))
  1798.             return;
  1799.         if ([13, 33, 34, 35, 36, 37, 38, 39, 40].indexOf(keyCode) == -1)
  1800.             return;
  1801.         var panel = Firebug.getElementPanel(target);
  1802.         var panelA11y = this.getPanelA11y(panel);
  1803.         if (!panelA11y)
  1804.             return;
  1805.         var box = panel.selectedSourceBox
  1806.         var caretDetails = this.getCaretDetails(target.ownerDocument);
  1807.         var lineNode = getAncestorByClass(caretDetails[0].parentNode, 'sourceRow');
  1808.         if (!lineNode )
  1809.             return;
  1810.         var lineNo = parseInt(lineNode.getElementsByClassName('sourceLine').item(0).textContent);
  1811.         box.a11yCaretLine = lineNo;
  1812.         box.a11yCaretOffset = caretDetails[1];
  1813.     },
  1814.  
  1815.     onScriptMouseUp : function(event)
  1816.     {
  1817.         var target = event.target;
  1818.         if (event.button !== 0)
  1819.             return;
  1820.         var panel = Firebug.getElementPanel(target);
  1821.         var panelA11y = this.getPanelA11y(panel);
  1822.         if (!panelA11y)
  1823.             return;
  1824.         var box = panel.selectedSourceBox
  1825.         var caretDetails = this.getCaretDetails(target.ownerDocument);
  1826.         var lineNode = null;
  1827.         if (caretDetails[0] && caretDetails[0].parentNode)
  1828.             lineNode = getAncestorByClass(caretDetails[0].parentNode, 'sourceRow');
  1829.         if (!lineNode )
  1830.             return;
  1831.         var lineNo = parseInt(lineNode.getElementsByClassName('sourceLine').item(0).textContent);
  1832.         box.a11yCaretLine = lineNo;
  1833.         box.a11yCaretOffset = caretDetails[1];
  1834.     },
  1835.  
  1836.     onBeforeViewportChange : function(panel)
  1837.     {
  1838.         var panelA11y = this.getPanelA11y(panel);
  1839.         if (!panelA11y)
  1840.             return;
  1841.         var box = panel.selectedSourceBox;
  1842.         if (!box)
  1843.             return;
  1844.         this.insertCaretIntoLine(panel, box);
  1845.     },
  1846.  
  1847.     insertCaretIntoLine : function(panel, box, lineNo, offset)
  1848.     {
  1849.         var panelA11y = this.getPanelA11y(panel);
  1850.         if (!panelA11y || !box)
  1851.             return;
  1852.         if (typeof lineNo == "undefined")
  1853.             lineNo = box.a11yCaretLine ?  box.a11yCaretLine : 0;
  1854.         //to prevent the caret from (partially) being placed just out of sight,
  1855.         //adjust the viewable line boundaries by 1 (unless the current line is the first or last line)
  1856.         var lineAdjust = lineNo == 0 || lineNo == box.totalMax ? 0 : 1;
  1857.         var firstLine = box.firstViewableLine + lineAdjust;
  1858.         var lastLine = box.lastViewableLine - lineAdjust;
  1859.         if (lineNo < (firstLine) || lineNo > lastLine)
  1860.             box.a11yCaretLine = lineNo = lineNo < firstLine ? firstLine : lastLine;
  1861.         var node = box.getLineNode(lineNo);
  1862.         if (!node)
  1863.             return;
  1864.         if (typeof offset =="undefined")
  1865.         {
  1866.             if (box.a11yCaretOffset)
  1867.                 offset = box.a11yCaretOffset;
  1868.             else
  1869.                 box.a11yCaretOffset = offset = 0;
  1870.         }
  1871.         var startNode = node.getElementsByClassName('sourceRowText').item(0)
  1872.         if (startNode && startNode.firstChild && startNode.firstChild.nodeType == 3)
  1873.         {
  1874.             startNode = startNode.firstChild;
  1875.             if (offset >= startNode.length)
  1876.                 box.a11yCaretOffset = offset = startNode.length - 1;
  1877.         }
  1878.         else
  1879.         {
  1880.             startNode = node; //offset is now the number of nodes, not characters within a text node
  1881.             offset = 1;
  1882.         }
  1883.         this.insertCaretToNode(panel, startNode, offset);
  1884.     },
  1885.  
  1886.     getCaretDetails : function(doc)
  1887.     {
  1888.         var sel = doc.defaultView.getSelection();
  1889.         return [sel.focusNode, sel.focusOffset];
  1890.     },
  1891.  
  1892.     onUpdateScriptLocation : function(panel, file)
  1893.     {
  1894.         var panelA11y = this.getPanelA11y(panel);
  1895.         if (!panelA11y)
  1896.             return;
  1897.         var box = panel.selectedSourceBox
  1898.         var viewport = panel.selectedSourceBox.getElementsByClassName('sourceViewport').item(0);
  1899.         box.tabIndex = -1;
  1900.         viewport.tabIndex = 0;
  1901.         viewport.setAttribute('role', 'textbox');
  1902.         viewport.setAttribute('aria-multiline', 'true');
  1903.         viewport.setAttribute('aria-readonly', 'true');
  1904.         fileName = getFileName(file.href);
  1905.         viewport.setAttribute('aria-label', $STRF('a11y.labels.source code for file', [fileName]));
  1906.         //bit ugly, but not sure how else I can get the caret into the sourcebox without a mouse
  1907.         var focusElem = Firebug.chrome.window.document.commandDispatcher.focusedElement;
  1908.         var line = box.getLineNode(box.firstViewableLine);
  1909.         if (!line)
  1910.             return;
  1911.         var node = line.getElementsByClassName("sourceRowText").item(0);
  1912.         this.insertCaretToNode(panel, node);
  1913.         this.focus(focusElem); // move focus back to where it was
  1914.     },
  1915.  
  1916.     insertCaretToNode : function(panel, node, startOffset)
  1917.     {
  1918.         if (!startOffset)
  1919.             startOffset = 0;
  1920.         var sel = panel.document.defaultView.getSelection();
  1921.         sel.removeAllRanges();
  1922.         var range = panel.document.createRange();
  1923.         range.setStart(node, startOffset);
  1924.         range.setEnd(node, startOffset);
  1925.         sel.addRange(range);
  1926.     },
  1927.  
  1928.     onScriptContextMenu : function(event)
  1929.     {
  1930.         if (event.button == 0 ) //i.e. keyboard, not right mouse click
  1931.         {
  1932.             //Try to find the line node based on the caret and manually trigger the context menu
  1933.             var panel = Firebug.getElementPanel(event.target);
  1934.             var panelA11y = this.getPanelA11y(panel);
  1935.             if (!panelA11y)
  1936.                 return;
  1937.             var sel = event.target.ownerDocument.defaultView.getSelection();
  1938.             var node = sel.focusNode.parentNode;
  1939.             var x = event.pageX
  1940.             if (x == 0)
  1941.             {
  1942.                 //TODO: This is ugly and way too inaccurate, how to get xy coordinates from selection object?
  1943.                 var charWidth = panelA11y.oneEmElem ? panelA11y.oneEmElem.clientWidth * 0.65: 7.5;
  1944.                 x = node.offsetLeft + sel.focusOffset * charWidth;
  1945.             }
  1946.             var y = event.pageY;
  1947.             if (y >= event.target.clientHeight)
  1948.             {
  1949.                y = node.offsetTop;
  1950.             }
  1951.             Firebug.chrome.window.document.popupNode = node;
  1952.             Firebug.chrome.$('fbContextMenu').openPopup(node.ownerDocument.body, "overlap", x, y, true);
  1953.             cancelEvent(event);
  1954.         }
  1955.     },
  1956.  
  1957.     onWatchPanelRefreshed : function(panel)
  1958.     {
  1959.         var panelA11y = this.getPanelA11y(panel);
  1960.         if (!panelA11y)
  1961.             return;
  1962.         var watchEditTrigger = panel.panelNode.getElementsByClassName('watchEditCell').item(0);
  1963.         if (watchEditTrigger)
  1964.             this.makeFocusable(watchEditTrigger, true);
  1965.     },
  1966.  
  1967.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1968.     // Call Stack Panel
  1969.  
  1970.     onstackCreated : function(panel)
  1971.     {
  1972.         var panelA11y = this.getPanelA11y(panel);
  1973.         if (!panelA11y)
  1974.             return;
  1975.         var rows = panel.panelNode.getElementsByClassName('focusRow');
  1976.         Array.forEach(rows, function(e,i,a){
  1977.             if ((panelA11y.lastIsDefault && i === rows.length - 1) || (!panelA11y.lastIsDefault && i === 0))
  1978.                 this.setPanelTabStop(panel, e);
  1979.             else
  1980.                 this.makeFocusable(e, false);
  1981.         }, this);
  1982.     },
  1983.  
  1984.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1985.     // Breakpoints Panel
  1986.  
  1987.     onBreakRowsRefreshed : function(panel, rootNode)
  1988.     {
  1989.         var rows = rootNode.getElementsByClassName('focusRow');
  1990.         for ( var i = 0; i < rows.length; i++)
  1991.         {
  1992.             this.makeFocusable(rows[i], i == 0);
  1993.             if (i == 0)
  1994.                 this.setPanelTabStop(panel, rows[i]);
  1995.         }
  1996.         var groupHeaders = rootNode.getElementsByClassName('breakpointHeader');
  1997.         for ( i = 0; i < groupHeaders.length; i++)
  1998.         {
  1999.             var listBox = getNextByClass(groupHeaders[i], 'breakpointsGroupListBox');
  2000.             if (listBox)
  2001.                 listBox.setAttribute('aria-label', groupHeaders[i].textContent);
  2002.         }
  2003.     },
  2004.  
  2005.     onScriptSearchMatchFound : function(panel, text, sourceBox, lineNo)
  2006.     {
  2007.         var panelA11y = this.getPanelA11y(panel);
  2008.         if (!panelA11y || !text)
  2009.             return;
  2010.         var matchFeedback = "";
  2011.         if (!sourceBox || lineNo === null)
  2012.             matchFeedback = $STRF('a11y.updates.no matches found', [text]);
  2013.         else
  2014.         {
  2015.             var line = sourceBox.getLine(panel.context, lineNo + 1);
  2016.             if (!line) line = "";
  2017.             matchFeedback = $STRF("a11y.updates.match found for on line", [text, lineNo + 1, getFileName(sourceBox.href)]);
  2018.         }
  2019.         this.updateLiveElem(panel, matchFeedback, true);
  2020.     },
  2021.  
  2022.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2023.     // Dom Panel
  2024.  
  2025.     onMemberRowsAdded: function(panel, rows)
  2026.     {
  2027.         if (!panel)
  2028.             panel = Firebug.getElementPanel(rows[0]);
  2029.         var panelA11y = this.getPanelA11y(panel);
  2030.         if (!panelA11y || !rows)
  2031.             return;
  2032.         var setSize;
  2033.         var posInset;
  2034.         var setSize = rows.length;
  2035.         var posInset = 0;
  2036.         for (var i = 0; i < rows.length; i++)
  2037.         {
  2038.             var makeTab = (panelA11y.lastIsDefault && i === rows.length - 1) || (!panelA11y.lastIsDefault && i === 0)
  2039.             this.prepareMemberRow(panel, rows[i], makeTab, ++posInset, setSize);
  2040.         }
  2041.     },
  2042.  
  2043.     onMemberRowSliceAdded : function(panel, borderRows, posInSet, setSize)
  2044.     {
  2045.         if (!borderRows)
  2046.             return;
  2047.         var startRow = borderRows[0];
  2048.         var endRow = borderRows[1];
  2049.         if (!panel)
  2050.             panel = Firebug.getElementPanel(startRow);
  2051.         var panelA11y = this.getPanelA11y(panel);
  2052.         if (!panelA11y)
  2053.             return;
  2054.         var reFocusId = panelA11y.reFocusId;
  2055.         var row = startRow;
  2056.         do
  2057.         {
  2058.             this.prepareMemberRow(panel, row, false, posInSet++, setSize, reFocusId)
  2059.             if (row === endRow)
  2060.                 break;
  2061.         }
  2062.         while (row = row.nextSibling);
  2063.     },
  2064.  
  2065.     prepareMemberRow : function(panel, row, makeTab, posInSet, setSize, reFocusId)
  2066.     {
  2067.         var panelA11y = this.getPanelA11y(panel);
  2068.         if (!panelA11y|| !row)
  2069.             return;
  2070.         if (!row.cells[2])
  2071.             return;
  2072.         var cellChild = row.cells[2].firstChild;
  2073.         if (cellChild)
  2074.         {
  2075.             if (hasClass(row, 'hasChildren'))
  2076.                 cellChild.setAttribute('aria-expanded', hasClass(row, 'opened'));
  2077.             if (makeTab)
  2078.                 this.modifyPanelRow(panel, cellChild, true);
  2079.             cellChild.setAttribute('role', 'treeitem');
  2080.             cellChild.setAttribute('aria-level', parseInt(row.getAttribute('level')) + 1);
  2081.             if (posInSet && setSize)
  2082.             {
  2083.                 cellChild.setAttribute('aria-setsize', setSize);
  2084.                 cellChild.setAttribute('aria-posinset', posInSet);
  2085.             }
  2086.             setClass(cellChild, 'focusRow');
  2087.             if (typeof reFocusId == "number" && row.rowIndex == reFocusId)
  2088.             {
  2089.                 this.modifyMemberRow(panel, cellChild, true);
  2090.                 this.focus(cellChild, true, true);
  2091.                 panelA11y.reFocusId = null;
  2092.             }
  2093.         }
  2094.     },
  2095.  
  2096.     modifyMemberRow : function(panel, row, inTabOrder)
  2097.     {
  2098.         var type = this.getObjectType(row)
  2099.         var labelCell = row.parentNode.previousSibling;
  2100.         row.setAttribute('aria-label', labelCell.textContent +
  2101.         ": " + " " + row.textContent + (type ? " (" + type + ")" : "" )) ;
  2102.         if (inTabOrder)
  2103.             this.setPanelTabStop(panel, row);
  2104.         else
  2105.             this.makeFocusable(row, false);
  2106.     },
  2107.  
  2108.     onBeforeDomUpdateSelection : function (panel)
  2109.     {
  2110.         var panelA11y = this.getPanelA11y(panel);
  2111.         if (!panelA11y)
  2112.             return;
  2113.         var focusNode = panel.document.activeElement;
  2114.         if (this.isDirCell(focusNode))
  2115.             panelA11y.reFocusId = focusNode.parentNode.parentNode.rowIndex;
  2116.     },
  2117.  
  2118.     onWatchEndEditing : function(panel, row)
  2119.     {
  2120.         var panelA11y = this.getPanelA11y(panel);
  2121.         if (!panelA11y)
  2122.             return;
  2123.         panelA11y.reFocusId = 2;
  2124.     },
  2125.  
  2126.     onDomSearchMatchFound : function (panel, text, matchRow)
  2127.     {
  2128.         var panelA11y = this.getPanelA11y(panel);
  2129.         if (!panelA11y || !text)
  2130.             return;
  2131.         var matchFeedback = "";
  2132.         if (matchRow && matchRow.cells)
  2133.         {
  2134.             var dirCell = matchRow.getElementsByClassName('focusRow').item(0);
  2135.             if (dirCell)
  2136.             {
  2137.                 this.modifyPanelRow(panel, dirCell);
  2138.                 var rowLabel = dirCell.getAttribute('aria-label');
  2139.                 matchFeedback = $STRF('a11y.updates.match found in dom property', [text, rowLabel]);
  2140.             }
  2141.         }
  2142.         else
  2143.             matchFeedback = $STRF('a11y.updates.no matches found', [text]);
  2144.         this.updateLiveElem(panel, matchFeedback, true);
  2145.     },
  2146.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2147.     // Net Panel
  2148.  
  2149.     isSubFocusRow : function(elem)
  2150.     {
  2151.         return hasClass(elem, 'focusRow') || hasClass(elem, 'wrappedText');
  2152.     },
  2153.  
  2154.     modifyNetRow : function(panel, row, inTabOrder)
  2155.     {
  2156.         var panelA11y = this.getPanelA11y(panel);
  2157.         if (!panelA11y || !row)
  2158.             return;
  2159.         if (this.isOuterFocusRow(row, true))
  2160.         {
  2161.             if (!hasClass(row, 'netInfoTabs'))
  2162.                 this.makeFocusable(row, inTabOrder);
  2163.             if ((hasClass(row, 'netRow') || hasClass(row, 'spyHeadTable')) && !row.hasAttribute('aria-expanded'))
  2164.                 row.setAttribute('aria-expanded', hasClass(row, 'opened') + "");
  2165.             var focusObjects = this.getFocusObjects(row);
  2166.             Array.forEach(focusObjects, function(e,i,a) {
  2167.                 this.makeFocusable(e);
  2168.                 if (hasClass(e, 'netTimeCol') && getAncestorByClass(e, 'fromCache'))
  2169.                     e.setAttribute('aria-label', e.textContent + " (" + $STR("a11y.labels.cached") +")"); // TODO : localize
  2170.             }, this);
  2171.         }
  2172.         else return;
  2173.     },
  2174.  
  2175.     getNetAncestorRow : function(elem, useSubRow)
  2176.     {
  2177.         return useSubRow ? getAncestorByClass(elem, 'subFocusRow') || getAncestorByClass(elem, 'netRow') : getAncestorByClass(elem, 'netRow');
  2178.     },
  2179.  
  2180.     onNetMouseDown : function(event)
  2181.     {
  2182.         var node = getAncestorByClass(event.target, 'focusRow');
  2183.         if (node)
  2184.             this.modifyPanelRow(Firebug.getElementPanel(node), node, false);
  2185.         else
  2186.         {
  2187.             node = getAncestorByClass(event.target, 'subFocusRow');
  2188.             if (!node)
  2189.                 return;
  2190.             var focusRow = node.getElementsByClassName('focusRow').item(0);
  2191.             if (!focusRow)
  2192.                 return
  2193.             this.modifyPanelRow(Firebug.getElementPanel(focusRow), focusRow, false);
  2194.             this.focus(focusRow);
  2195.         }
  2196.     },
  2197.  
  2198.     onNetFocus : function(e) {
  2199.         var target = e.target;
  2200.         var panel = Firebug.getElementPanel(target);
  2201.         var panelA11y = this.getPanelA11y(panel);
  2202.         if (!panelA11y)
  2203.             return;
  2204.         if (!hasClass(target, 'netCol') && !hasClass(target, 'netHeaderCell'))
  2205.         {
  2206.             return;
  2207.         }
  2208.         if (hasClass(target, 'netHrefCol'))
  2209.         {
  2210.             var hrefLabel = target.getElementsByClassName('netHrefLabel').item(0);
  2211.             var fullHrefLabel = target.getElementsByClassName('netFullHrefLabel').item(0);
  2212.             if (hrefLabel && fullHrefLabel)
  2213.             {
  2214.                 setClass(fullHrefLabel, 'a11yShowFullLabel');
  2215.                 fullHrefLabel.style.marginTop = (hrefLabel.offsetHeight  + 4) + "px";
  2216.             return;
  2217.             }
  2218.         }
  2219.         var rangeParent = getAncestorByClass(target, 'netRow');
  2220.         var browser = Firebug.chrome.getPanelBrowser(panel);
  2221.         // these two lines are necessary, because otherwise the infoTip will not have the correct dimensions when it's positioned, and the contents
  2222.         // could be placed outside FB's viewport (making it impossible to read for keyboard users)
  2223.         panel.showInfoTip(browser.infoTip, target, target.offsetLeft, target.offsetTop, rangeParent, 0); //will be called again in showInfoTip
  2224.         browser.infoTip.setAttribute("active", "true");
  2225.         var left = hasClass(target, 'netTimeCol') ? target.offsetLeft - browser.infoTip.offsetWidth - 12 : target.offsetLeft + target.offsetWidth - 4;
  2226.         Firebug.InfoTip.showInfoTip(browser.infoTip, panel, target, left, target.offsetTop - panel.panelNode.scrollTop - 12, rangeParent, 0);
  2227.     },
  2228.  
  2229.     onNetBlur : function(e) {
  2230.         var target = e.target;
  2231.         var panel = Firebug.getElementPanel(target);
  2232.         var panelA11y = this.getPanelA11y(panel);
  2233.         if (!panelA11y)
  2234.             return;
  2235.         if (hasClass(target, 'netHrefCol'))
  2236.         {
  2237.             var hrefLabel = target.getElementsByClassName('netHrefLabel').item(0);
  2238.             var fullHrefLabel = target.getElementsByClassName('netFullHrefLabel').item(0);
  2239.             if (hrefLabel && fullHrefLabel)
  2240.             {
  2241.                 removeClass(fullHrefLabel, 'a11yShowFullLabel');
  2242.                 fullHrefLabel.style.marginTop = "0px";
  2243.             }
  2244.         }
  2245.         var browser = Firebug.chrome.getPanelBrowser(panel);
  2246.         Firebug.InfoTip.hideInfoTip(browser.infoTip);
  2247.     },
  2248.  
  2249.     onNetMatchFound : function(panel, text, row)
  2250.     {
  2251.         //TODO localize for 1.5
  2252.         var panelA11y = this.getPanelA11y(panel);
  2253.         if (!panelA11y)
  2254.             return;
  2255.         var matchFeedback = "";
  2256.         if (!row)
  2257.             matchFeedback = $STRF('a11y.updates.no matches found', [text]);
  2258.         else
  2259.         {
  2260.             var foundWhere = '';
  2261.             var parentRow = getAncestorByClass(row, "netRow");
  2262.             if (!parentRow)
  2263.             {
  2264.                 parentRow = getAncestorByClass(row, "netInfoRow");
  2265.                 if (parentRow)
  2266.                     parentRow = parentRow.previousSibling;
  2267.             }
  2268.             if (hasClass(row, "netHrefLabel"))
  2269.                 foundWhere = $STR("net.header.URL");
  2270.             else if (hasClass(row, "netStatusLabel"))
  2271.                 foundWhere = $STR("net.header.Status");
  2272.             else if (hasClass(row, "netDomainLabel"))
  2273.                 foundWhere = $STR("net.header.Domain");
  2274.             else if (hasClass(row, "netSizeLabel"))
  2275.                 foundWhere = $STR("net.header.Size");
  2276.             else if (hasClass(row, "netTimeLabel"))
  2277.                 foundWhere = $STR("net.header.Timeline");
  2278.             else
  2279.                 foundWhere = "request details";
  2280.             if (parentRow && parentRow.repObject)
  2281.             {
  2282.                 var file = parentRow.repObject;
  2283.                 var href =  (file.method ? file.method.toUpperCase() : "?") + " " + getFileName(file.href);
  2284.                 matchFeedback = $STRF("a11y.updates.match found in net row",[text, href, foundWhere, row.textContent]);
  2285.             }
  2286.             else if (getAncestorByClass(row, "netSummaryRow"))
  2287.                 matchFeedback = $STRF("a11y.updates.match found in net summary row",[text, row.textContent]);
  2288.         }
  2289.         this.updateLiveElem(panel, matchFeedback, true); //should not use alert
  2290.     },
  2291.  
  2292.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2293.     // Panel Navigation
  2294.  
  2295.     insertHiddenText : function(panel, elem, text, asLastNode, id)
  2296.     {
  2297.         var span = panel.document.createElement('span');
  2298.         span.className ="offScreen";
  2299.         span.textContent = text;
  2300.         if (id)
  2301.             span.id = id;
  2302.         if (asLastNode)
  2303.             elem.appendChild(span);
  2304.         else
  2305.             elem.insertBefore(span, elem.firstChild);
  2306.     },
  2307.  
  2308.     getLogRowType : function(elem)
  2309.     {
  2310.         var type = "";
  2311.         if (!elem)
  2312.             return type;
  2313.         var className = elem.className.match(/\logRow-(\w+)\b/);
  2314.         if (className)
  2315.             type = className[1];
  2316.         if (!type)
  2317.         {
  2318.             if (hasClass(elem, 'errorTitle'))
  2319.                 type = "detailed error";
  2320.             else if (hasClass(elem, 'errorSourceBox'))
  2321.                 type = "error source line";
  2322.             else
  2323.                 type = this.getObjectType(elem);
  2324.         }
  2325.         if (type == "stackFrame")
  2326.             type="";
  2327.         return type;
  2328.     },
  2329.  
  2330.     getObjectType : function(elem)
  2331.     {
  2332.         var type = "";
  2333.         if (elem.nodeName == "img")
  2334.             return type;
  2335.         var className = elem.className.match(/\bobject(Box|Link)-(\w+)/);
  2336.         if (className)
  2337.             type = className[2];
  2338.         if (type == "null" || type == "undefined")
  2339.             type = "";
  2340.         else if (type == "number" && (elem.textContent == "true" || elem.textContent == "false"))
  2341.             type = "boolean";
  2342.         else if ((type == "" || type == "object") && elem.repObject)
  2343.         {
  2344.             try
  2345.             {
  2346.                 var obj = elem.repObject;
  2347.                 if (!obj)
  2348.                     return type;
  2349.                 type = typeof obj;
  2350.                 if (obj instanceof Array)
  2351.                     type = "array";
  2352.                 if (typeof obj.lineNo != "undefined")
  2353.                     type = "function call";
  2354.             }
  2355.             catch(e)
  2356.             {}
  2357.         }
  2358.         return type;
  2359.     },
  2360.  
  2361.     modifyPanelRow : function (panel, row, inTabOrder)
  2362.     {
  2363.         if (hasClass(row, 'a11yModified'))
  2364.             return;
  2365.         var panelA11y = this.getPanelA11y(panel);
  2366.         if (!panelA11y || !row)
  2367.             return;
  2368.         switch (panel.name)
  2369.         {
  2370.             case 'console':
  2371.             case 'dom':
  2372.             case 'domSide':
  2373.                 this.modifyConsoleRow(panel,row, inTabOrder);
  2374.                 break;
  2375.             case 'css':
  2376.             case 'stylesheet':
  2377.             case 'computed':
  2378.                 this.modifyCSSRow(panel, row, inTabOrder);
  2379.                 break;
  2380.             case 'net':
  2381.                 this.modifyNetRow(panel, row, inTabOrder);
  2382.                break;
  2383.         }
  2384.         setClass(row, 'a11yModified');
  2385.     },
  2386.  
  2387.     focusSiblingRow : function(panel, target, goUp)
  2388.     {
  2389.         var newRow = this[goUp ? 'getPreviousByClass' : 'getNextByClass'](target, 'focusRow', true, panel.panelNode)
  2390.         if (!newRow)
  2391.             return;
  2392.         this.focusPanelRow(panel, newRow)
  2393.     },
  2394.  
  2395.     focusPageSiblingRow : function(panel, target, goUp)
  2396.     {
  2397.         var rows = this.getFocusRows(panel);
  2398.         var index = this.getRowIndex(rows, target);
  2399.         var newRow = this.getValidRow(rows, goUp ? index - 10 : index + 10);
  2400.         this.focusPanelRow(panel, newRow);
  2401.     },
  2402.  
  2403.     focusEdgeRow : function(panel, target, goUp)
  2404.     {
  2405.         var rows = this.getFocusRows(panel);
  2406.         var newRow = this.getValidRow(rows, goUp ? 0 : rows.length -1);
  2407.         this.focusPanelRow(panel, newRow);
  2408.     },
  2409.  
  2410.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2411.     // Utils
  2412.  
  2413.     onPanelFocus : function(event)
  2414.     {
  2415.         var panel = Firebug.getElementPanel(event.target);
  2416.         var panelA11y = this.getPanelA11y(panel);
  2417.         if (!panelA11y)
  2418.             return;
  2419.         var target = event.target;
  2420.         if (this.isTabWorthy(target) && target !== this.getPanelTabStop(panel))
  2421.             this.setPanelTabStop(panel, target);
  2422.         if (target.getAttribute("role") =="gridcell" || target.getAttribute("role") =="rowheader" || target.getAttribute("role") =="columnheader")
  2423.         {
  2424.             var cell = target.nodeName.toLowerCase() == "td" || target.nodeName.toLowerCase() == "th" ? target : target.parentNode;
  2425.             panelA11y.cellIndex = cell.cellIndex !== undefined ? cell.cellIndex : undefined;
  2426.         }
  2427.         else
  2428.         {
  2429.             if (hasClass(target, 'netInfoTab'))
  2430.                 this.dispatchMouseEvent(target, 'click');
  2431.             panelA11y.cellIndex = undefined; //reset if no longer in grid
  2432.         }
  2433.     },
  2434.  
  2435.     getFocusRows : function(panel)
  2436.     {
  2437.         var nodes = panel.panelNode.getElementsByClassName('focusRow');
  2438.         return Array.filter(nodes, function(e,i,a){return this.isVisibleByStyle(e) && isVisible(e);}, this);
  2439.     },
  2440.  
  2441.     getLastFocusChild : function(target)
  2442.     {
  2443.         var focusChildren = target.getElementsByClassName('focusRow');
  2444.         return focusChildren.length > 0 ? focusChildren[focusChildren.length -1] : null;
  2445.     },
  2446.  
  2447.     getFirstFocusChild : function(target)
  2448.     {
  2449.         var focusChildren = target.getElementsByClassName('focusRow');
  2450.         return focusChildren.length > 0 ? focusChildren[0] : null;
  2451.     },
  2452.  
  2453.     focus : function(elem, noVisiCheck, needsMoreTime)
  2454.     {
  2455.         if (isElement(elem) && (noVisiCheck || this.isVisibleByStyle(elem)))
  2456.             FirebugContext.setTimeout(function(){
  2457.                 elem.focus()
  2458.                 }, needsMoreTime ? 500 :10);
  2459.     },
  2460.  
  2461.     makeFocusable : function(elem, inTabOrder)
  2462.     {
  2463.         if (elem)
  2464.             elem.setAttribute('tabindex', inTabOrder ? '0' : '-1');
  2465.     },
  2466.  
  2467.     makeUnfocusable : function(elem)
  2468.     {
  2469.         if (elem)
  2470.             elem.removeAttribute('tabindex');
  2471.     },
  2472.  
  2473.     reportFocus : function(event)
  2474.     {
  2475.         FBTrace.sysout('focus: ' + event.target.nodeName + "#" + event.target.id + "." + event.target.className, event.target);
  2476.     },
  2477.  
  2478.     dispatchMouseEvent : function (node, eventType, clientX, clientY, button)
  2479.     {
  2480.         if (!clientX)
  2481.             clientX = 0;
  2482.         if (!clientY)
  2483.             clientY = 0;
  2484.         if (!button)
  2485.             button = 0;
  2486.         if (typeof node == "string")
  2487.             node = $(node);
  2488.         var doc = node.ownerDocument;
  2489.         var event = doc.createEvent('MouseEvents');
  2490.         event.initMouseEvent(eventType, true, true, doc.defaultView,
  2491.             0, 0, 0, clientX, clientY, false, false, false, false, button, null);
  2492.         node.dispatchEvent(event);
  2493.     },
  2494.  
  2495.     isVisibleByStyle : function (elem)
  2496.     {
  2497.         if (!elem || elem.nodeType != 1)
  2498.             return false;
  2499.         var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
  2500.         return style.visibility !== "hidden" && style.display !== "none" ;
  2501.     },
  2502.  
  2503.     isTabWorthy : function (elem)
  2504.     {
  2505.         return this.isFocusRow(elem) || this.isFocusObject(elem);
  2506.     },
  2507.  
  2508.     isOuterFocusRow : function(elem, includeSubRow)
  2509.     {
  2510.         return includeSubRow ? this.isSubFocusRow(elem) : hasClass(elem, 'outerFocusRow');
  2511.     },
  2512.  
  2513.     isProfileRow : function(elem)
  2514.     {
  2515.         return hasClass(elem, 'profileRow');
  2516.     },
  2517.  
  2518.     isFocusRow : function(elem)
  2519.     {
  2520.         return hasClass(elem, 'focusRow');
  2521.     },
  2522.  
  2523.     isFocusObject : function(elem)
  2524.     {
  2525.         return hasClass(elem, 'a11yFocus');
  2526.     },
  2527.  
  2528.     isFocusNoTabObject : function(elem)
  2529.     {
  2530.         return hasClass(elem, 'a11yFocusNoTab');
  2531.     },
  2532.  
  2533.     isDirCell : function(elem)
  2534.     {
  2535.         return hasClass(elem.parentNode, 'memberValueCell');
  2536.     },
  2537.  
  2538.     panelHasFocus : function(panel)
  2539.     {
  2540.         if (!panel || !panel.context)
  2541.             return false;
  2542.         var focusedElement = Firebug.chrome.window.document.commandDispatcher.focusedElement;
  2543.         var focusedPanel = Firebug.getElementPanel(focusedElement)
  2544.         return focusedPanel && (focusedPanel.name == panel.name);
  2545.     },
  2546.  
  2547.     getPanelA11y : function(panel, create)
  2548.     {
  2549.         var a11yPanels, panelA11y;
  2550.         if (!this.isEnabled() || !panel || !panel.name || !panel.context)
  2551.             return false;
  2552.         a11yPanels = panel.context.a11yPanels;
  2553.         if (!a11yPanels)
  2554.             a11yPanels = panel.context.a11yPanels = {};
  2555.         panelA11y = a11yPanels[panel.name];
  2556.         if (!panelA11y)
  2557.         {
  2558.             if (create)
  2559.                 panelA11y = a11yPanels[panel.name] = {};
  2560.             else
  2561.                 return false;
  2562.         }
  2563.         return panelA11y
  2564.     },
  2565.  
  2566.     //these utils are almost the same as their FBL namesakes ,
  2567.     //except that that the routine skips containers that are not visible (rather than wasting time on their childnodes)
  2568.  
  2569.     getPreviousByClass : function (node, className, downOnly, maxRoot)
  2570.     {
  2571.         if (!node)
  2572.             return null;
  2573.         function criteria(node) { return node.nodeType == 1 && hasClass(node, className); }
  2574.         for (var sib = node.previousSibling; sib; sib = sib.previousSibling)
  2575.         {
  2576.             if (!this.isVisibleByStyle(sib) || !isVisible(sib))
  2577.                 continue;
  2578.             var prev = this.findPreviousUp(sib, criteria);
  2579.             if (prev)
  2580.                 return prev;
  2581.             if (criteria(sib))
  2582.                 return sib;
  2583.         }
  2584.         if (!downOnly)
  2585.         {
  2586.             var next = this.findPreviousUp(node, criteria);
  2587.             if (next)
  2588.                 return next;
  2589.         }
  2590.         if (node.parentNode && node.parentNode != maxRoot)
  2591.         {
  2592.             if (criteria(node.parentNode))
  2593.                 return node.parentNode;
  2594.             return this.getPreviousByClass(node.parentNode, className, true);
  2595.         }
  2596.     },
  2597.  
  2598.     getNextByClass : function (node, className, upOnly, maxRoot)
  2599.     {
  2600.         if (!node)
  2601.             return null;
  2602.         function criteria(node) { return node.nodeType == 1 && hasClass(node, className); }
  2603.         if (!upOnly)
  2604.         {
  2605.             var next = this.findNextDown(node, criteria);
  2606.             if (next)
  2607.                 return next;
  2608.         }
  2609.         for (var sib = node.nextSibling; sib; sib = sib.nextSibling)
  2610.         {
  2611.             if (!this.isVisibleByStyle(sib) || !isVisible(sib))
  2612.                 continue;
  2613.             if (criteria(sib))
  2614.                 return sib;
  2615.             var next = this.findNextDown(sib, criteria);
  2616.             if (next)
  2617.                 return next;
  2618.         }
  2619.         if (node.parentNode && node.parentNode != maxRoot)
  2620.             return this.getNextByClass(node.parentNode, className, true);
  2621.     },
  2622.  
  2623.     findNextDown : function(node, criteria)
  2624.     {
  2625.         if (!node)
  2626.             return null;
  2627.         for (var child = node.firstChild; child; child = child.nextSibling)
  2628.         {
  2629.             if (!this.isVisibleByStyle(child) || !isVisible(child))
  2630.                 continue;
  2631.             if (criteria(child))
  2632.                 return child;
  2633.             var next = this.findNextDown(child, criteria);
  2634.             if (next)
  2635.                 return next;
  2636.         }
  2637.     },
  2638.  
  2639.     findPreviousUp : function(node, criteria)
  2640.     {
  2641.         if (!node)
  2642.             return null;
  2643.         for (var child = node.lastChild; child; child = child.previousSibling)
  2644.         {
  2645.             if (!this.isVisibleByStyle(child) || !isVisible(child))
  2646.                 continue;
  2647.             var next = this.findPreviousUp(child, criteria);
  2648.             if (next)
  2649.                 return next;
  2650.             if (criteria(child))
  2651.                 return child;
  2652.         }
  2653.     }
  2654. });
  2655.  
  2656. // ************************************************************************************************
  2657. // Registration
  2658.  
  2659. Firebug.registerModule(Firebug.A11yModel);
  2660. }});
  2661.